fix: EntityCollection iterator O(n²) → O(n) with 100× speedup (closes #159)
Problem: EntityCollection iterator used index-based access on std::list, causing O(n) traversal per element access (O(n²) total for iteration). Root cause: Each call to next() started from begin() and advanced index steps: std::advance(l_begin, self->index-1); // O(index) for linked list! Solution: - Store actual std::list iterators (current, end) instead of index - Increment iterator directly in next() - O(1) operation - Cache Entity and Iterator type lookups to avoid repeated dict lookups Benchmark results (2,000 entities): - Before: 13.577ms via EntityCollection - After: 0.131ms via EntityCollection - Speedup: 103× 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
fcc0376f31
commit
8f2407b518
3 changed files with 39 additions and 31 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -26,6 +26,7 @@ mcrogueface.github.io
|
||||||
scripts/
|
scripts/
|
||||||
tcod_reference
|
tcod_reference
|
||||||
.archive
|
.archive
|
||||||
|
.mcp.json
|
||||||
|
|
||||||
# Keep important documentation and tests
|
# Keep important documentation and tests
|
||||||
!CLAUDE.md
|
!CLAUDE.md
|
||||||
|
|
|
||||||
|
|
@ -2113,38 +2113,37 @@ int UIEntityCollectionIter::init(PyUIEntityCollectionIterObject* self, PyObject*
|
||||||
|
|
||||||
PyObject* UIEntityCollectionIter::next(PyUIEntityCollectionIterObject* self)
|
PyObject* UIEntityCollectionIter::next(PyUIEntityCollectionIterObject* self)
|
||||||
{
|
{
|
||||||
|
// Check for collection modification during iteration
|
||||||
if (self->data->size() != self->start_size)
|
if (self->data->size() != self->start_size)
|
||||||
{
|
{
|
||||||
PyErr_SetString(PyExc_RuntimeError, "collection changed size during iteration");
|
PyErr_SetString(PyExc_RuntimeError, "collection changed size during iteration");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self->index > self->start_size - 1)
|
// Check if we've reached the end
|
||||||
|
if (self->current == self->end)
|
||||||
{
|
{
|
||||||
PyErr_SetNone(PyExc_StopIteration);
|
PyErr_SetNone(PyExc_StopIteration);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
self->index++;
|
|
||||||
auto vec = self->data.get();
|
// Get current element and advance iterator - O(1) operation
|
||||||
if (!vec)
|
auto target = *self->current;
|
||||||
{
|
++self->current;
|
||||||
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
// Advance list iterator since Entities are stored in a list, not a vector
|
|
||||||
auto l_begin = (*vec).begin();
|
|
||||||
std::advance(l_begin, self->index-1);
|
|
||||||
auto target = *l_begin;
|
|
||||||
|
|
||||||
// Return the stored Python object if it exists (preserves derived types)
|
// Return the stored Python object if it exists (preserves derived types)
|
||||||
if (target->self != nullptr) {
|
if (target->self != nullptr) {
|
||||||
Py_INCREF(target->self);
|
Py_INCREF(target->self);
|
||||||
return target->self;
|
return target->self;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise create and return a new Python Entity object
|
// Otherwise create and return a new Python Entity object
|
||||||
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity");
|
// Cache the Entity type to avoid repeated dictionary lookups (#159)
|
||||||
auto o = (PyUIEntityObject*)type->tp_alloc(type, 0);
|
static PyTypeObject* cached_entity_type = nullptr;
|
||||||
|
if (!cached_entity_type) {
|
||||||
|
cached_entity_type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity");
|
||||||
|
}
|
||||||
|
auto o = (PyUIEntityObject*)cached_entity_type->tp_alloc(cached_entity_type, 0);
|
||||||
auto p = std::static_pointer_cast<UIEntity>(target);
|
auto p = std::static_pointer_cast<UIEntity>(target);
|
||||||
o->data = p;
|
o->data = p;
|
||||||
return (PyObject*)o;
|
return (PyObject*)o;
|
||||||
|
|
@ -2153,9 +2152,12 @@ PyObject* UIEntityCollectionIter::next(PyUIEntityCollectionIterObject* self)
|
||||||
PyObject* UIEntityCollectionIter::repr(PyUIEntityCollectionIterObject* self)
|
PyObject* UIEntityCollectionIter::repr(PyUIEntityCollectionIterObject* self)
|
||||||
{
|
{
|
||||||
std::ostringstream ss;
|
std::ostringstream ss;
|
||||||
if (!self->data) ss << "<UICollectionIter (invalid internal object)>";
|
if (!self->data) ss << "<UIEntityCollectionIter (invalid internal object)>";
|
||||||
else {
|
else {
|
||||||
ss << "<UICollectionIter (" << self->data->size() << " child objects, @ index " << self->index << ")>";
|
// Calculate current position by distance from end
|
||||||
|
auto remaining = std::distance(self->current, self->end);
|
||||||
|
auto total = self->data->size();
|
||||||
|
ss << "<UIEntityCollectionIter (" << (total - remaining) << "/" << total << " entities)>";
|
||||||
}
|
}
|
||||||
std::string repr_str = ss.str();
|
std::string repr_str = ss.str();
|
||||||
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
|
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
|
||||||
|
|
@ -3060,26 +3062,30 @@ int UIEntityCollection::init(PyUIEntityCollectionObject* self, PyObject* args, P
|
||||||
|
|
||||||
PyObject* UIEntityCollection::iter(PyUIEntityCollectionObject* self)
|
PyObject* UIEntityCollection::iter(PyUIEntityCollectionObject* self)
|
||||||
{
|
{
|
||||||
// Get the iterator type from the module to ensure we have the registered version
|
// Cache the iterator type to avoid repeated dictionary lookups (#159)
|
||||||
PyTypeObject* iterType = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "UIEntityCollectionIter");
|
static PyTypeObject* cached_iter_type = nullptr;
|
||||||
if (!iterType) {
|
if (!cached_iter_type) {
|
||||||
PyErr_SetString(PyExc_RuntimeError, "Could not find UIEntityCollectionIter type in module");
|
cached_iter_type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "UIEntityCollectionIter");
|
||||||
return NULL;
|
if (!cached_iter_type) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "Could not find UIEntityCollectionIter type in module");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
// Keep a reference to prevent it from being garbage collected
|
||||||
|
Py_INCREF(cached_iter_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate new iterator instance
|
// Allocate new iterator instance
|
||||||
PyUIEntityCollectionIterObject* iterObj = (PyUIEntityCollectionIterObject*)iterType->tp_alloc(iterType, 0);
|
PyUIEntityCollectionIterObject* iterObj = (PyUIEntityCollectionIterObject*)cached_iter_type->tp_alloc(cached_iter_type, 0);
|
||||||
|
|
||||||
if (iterObj == NULL) {
|
if (iterObj == NULL) {
|
||||||
Py_DECREF(iterType);
|
|
||||||
return NULL; // Failed to allocate memory for the iterator object
|
return NULL; // Failed to allocate memory for the iterator object
|
||||||
}
|
}
|
||||||
|
|
||||||
iterObj->data = self->data;
|
iterObj->data = self->data;
|
||||||
iterObj->index = 0;
|
iterObj->current = self->data->begin(); // Start at beginning
|
||||||
|
iterObj->end = self->data->end(); // Cache end iterator
|
||||||
iterObj->start_size = self->data->size();
|
iterObj->start_size = self->data->size();
|
||||||
|
|
||||||
Py_DECREF(iterType);
|
|
||||||
return (PyObject*)iterObj;
|
return (PyObject*)iterObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -222,8 +222,9 @@ public:
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
std::shared_ptr<std::list<std::shared_ptr<UIEntity>>> data;
|
std::shared_ptr<std::list<std::shared_ptr<UIEntity>>> data;
|
||||||
int index;
|
std::list<std::shared_ptr<UIEntity>>::iterator current; // Actual list iterator - O(1) increment
|
||||||
int start_size;
|
std::list<std::shared_ptr<UIEntity>>::iterator end; // End iterator for bounds check
|
||||||
|
int start_size; // For detecting modification during iteration
|
||||||
} PyUIEntityCollectionIterObject;
|
} PyUIEntityCollectionIterObject;
|
||||||
|
|
||||||
class UIEntityCollectionIter {
|
class UIEntityCollectionIter {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue