cocos2d-0001: EventDispatcher _toRemovedListeners std::find O(L*R) MEDIUM 2.4x cocos2d-0002: PhysicsWorld collisionBeginCallback std::find O(J_body*J_world) MEDIUM 11.6x cocos2d-0003: BoneNode::visit _boneSkins.contains O(C*S) per frame MEDIUM 7.3x MOAD-0002 (Intertangle): heavy singleton pattern (Director, etc.) but architectural, not patchable MOAD-0003 (Leaked Context): no thread_local usage, CLEAN MOAD-0004 (Logged Secret): no credential logging, CLEAN MOAD-0005 (Thundering Herd): TextureCache uses unordered_map, CLEAN 6/6 unit tests PASS.
90 lines
3.1 KiB
C++
90 lines
3.1 KiB
C++
// Unit test for cocos2d-0003: BoneNode::visit _boneSkins.contains O(C*S) per frame
|
|
// Defect: In the per-frame render traversal (visit), for each child node,
|
|
// _boneSkins.contains(node) does O(S) linear scan of the skins vector.
|
|
// With C children and S skins per bone, this is O(C*S) per bone per frame.
|
|
// Fix: Maintain an std::unordered_set<Node*> _boneSkinSet alongside the vector
|
|
// for O(1) membership test.
|
|
|
|
#include <vector>
|
|
#include <unordered_set>
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <cassert>
|
|
#include <algorithm>
|
|
|
|
struct Node { int id; };
|
|
|
|
// Defect: linear scan of skins vector for each child
|
|
int defectVisit(const std::vector<Node*>& children,
|
|
const std::vector<Node*>& boneSkins) {
|
|
int rendered = 0;
|
|
for (auto* node : children) {
|
|
// O(S) linear scan
|
|
if (std::find(boneSkins.begin(), boneSkins.end(), node) != boneSkins.end())
|
|
continue;
|
|
rendered++;
|
|
}
|
|
return rendered;
|
|
}
|
|
|
|
// Fixed: hash set for O(1) lookup
|
|
int fixedVisit(const std::vector<Node*>& children,
|
|
const std::unordered_set<Node*>& boneSkinSet) {
|
|
int rendered = 0;
|
|
for (auto* node : children) {
|
|
if (boneSkinSet.count(node) > 0)
|
|
continue;
|
|
rendered++;
|
|
}
|
|
return rendered;
|
|
}
|
|
|
|
int main() {
|
|
const int C = 500; // children per bone (complex character)
|
|
const int S = 200; // skins per bone
|
|
const int FRAMES = 1000; // per-frame render traversal
|
|
|
|
std::vector<Node> pool(C);
|
|
for (int i = 0; i < C; i++) pool[i].id = i;
|
|
|
|
std::vector<Node*> children;
|
|
for (int i = 0; i < C; i++) children.push_back(&pool[i]);
|
|
|
|
// First S children are skins
|
|
std::vector<Node*> boneSkins;
|
|
std::unordered_set<Node*> boneSkinSet;
|
|
for (int i = 0; i < S; i++) {
|
|
boneSkins.push_back(&pool[i]);
|
|
boneSkinSet.insert(&pool[i]);
|
|
}
|
|
|
|
// === Defect ===
|
|
auto t0 = std::chrono::high_resolution_clock::now();
|
|
int defectTotal = 0;
|
|
for (int f = 0; f < FRAMES; f++)
|
|
defectTotal += defectVisit(children, boneSkins);
|
|
auto t1 = std::chrono::high_resolution_clock::now();
|
|
double defectUs = std::chrono::duration<double, std::micro>(t1 - t0).count();
|
|
|
|
// === Fixed ===
|
|
auto t2 = std::chrono::high_resolution_clock::now();
|
|
int fixedTotal = 0;
|
|
for (int f = 0; f < FRAMES; f++)
|
|
fixedTotal += fixedVisit(children, boneSkinSet);
|
|
auto t3 = std::chrono::high_resolution_clock::now();
|
|
double fixedUs = std::chrono::duration<double, std::micro>(t3 - t2).count();
|
|
|
|
assert(defectTotal == fixedTotal);
|
|
int expectedRendered = (C - S) * FRAMES;
|
|
assert(defectTotal == expectedRendered);
|
|
|
|
double ratio = defectUs / fixedUs;
|
|
printf("cocos2d-0003: BoneNode::visit _boneSkins.contains O(C*S) per frame\n");
|
|
printf(" C=%d children, S=%d skins, FRAMES=%d\n", C, S, FRAMES);
|
|
printf(" defect: %.1f us\n", defectUs);
|
|
printf(" fixed: %.1f us\n", fixedUs);
|
|
printf(" ratio: %.1fx\n", ratio);
|
|
printf(" PASS: %s\n", (defectTotal == expectedRendered) ? "true" : "false");
|
|
|
|
return (defectTotal == expectedRendered) ? 0 : 1;
|
|
}
|