English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

un

gäst
1 / ?

Hur kodsediment bildas

Sedimentär bergart bildas genom avlagring, inte explosion. Varje lager: korrekt för sin tid. Varje lager: äldre än det ovanför. De äldsta lagren förstenades innan ekosystemet mognade runt dem. Inget fel orsakade dem. Tiden orsakade dem.

MOAD-0001 fungerar på samma sätt.

MOAD-0001 Sedimentlager: kod kopierad över årtionden när N växer

Bildningsberättelsen

En grafgenomgång skriven 1993:

List<Node> visited = new ArrayList<>();
Stack<Node> stack = new Stack<>();
stack.push(start);
while (!stack.isEmpty()) {
Node n = stack.pop();
if (!visited.contains(n)) {  // O(N) linjär sökning
visited.add(n);
for (Node neighbor : n.getNeighbors())
stack.push(neighbor);
}
}

Den här koden: korrekt. Tester: godkända. År 1993 hade graferna 50 noder.

50 noder: 50 × 25 genomsökta i snitt = 1 250 operationer. Osynligt.

Koden hamnade i versionshantering. Kopierades in i handledningar. Paketerades i bibliotek. Levererades i byggverktyg, pakethanterare och beroendehanterare. År 2024 kördes samma mönster på beroendegrafer med 50 000 noder.

50 000 noder: 50 000 × 25 000 genomsökta i snitt = 1 250 000 000 operationer.
En 1-sekunders-byggtid blir 17 minuter.

Koden ändrades inte. Världen omkring den växte. Varje lager av sedimentet: korrekt vid avsättning. Kostsamt vid utgrävning.

Ditt sediment

Sedimentär kod innehåller inte ett logikfel. Den innehåller ett prestandaantagande som slutade gälla när skalan ändrades. Koden ger korrekta resultat; endast kostnaden har ändrats.

Denna distinktion är viktig för diagnostik. Ett logikfel ger felaktiga svar. Ett sedimentärt fel ger korrekta svar till en oacceptabel kostnad.

Sedimentär kod: korrekt kod som blir dyrare när skalan runt den ändras. Ge ett konkret exempel från programvara du har använt eller skrivit där något fungerade i liten skala men blev en flaskhals i stor skala. Vad ändrades: koden, datastorleken eller användningsmönstret?

Fem former av MOAD-0001

MOAD-0001 förekommer i fem dokumenterade former över 60+ mjukvaruekosystem. Strukturen: ett medlemskapstest med en sekventiell behållare, nästlad inuti en loop över samma eller relaterad samling.

De fem formerna

DomänMönsterSekventiell behållareKorrekt behållare
Graftraverseringif (!stack.contains(n)) i DFS/TarjanArrayListHashSet
Avduplicering av rutter/händelserTAILQ_FOREACH strcmp per begäranlänkad listaHashMap
KollisionsspårningfindLinearSearch() per fysiktickarrayunordered_set
ResursallokeringsfilterList.contains() i strömfilterArrayListHashSet
A*-sökvägsöppningslistaLocalVector::find() per grannevectorlagrad heap-index

Alla fem delar samma struktur: ett medlemskapstest nästlat i en loop, med en behållare som kräver en linjär sökning för att besvara medlemskapsfrågan.

Nyckelordslistan för scanning

Att granska för MOAD-0001 innebär att söka efter medlemskapstest-nyckelord inuti loopar:

- Python: in med en list-variabel, .count(), list.index()

- Java: ArrayList.contains(), List.contains(), LinkedList.contains()

- JavaScript: Array.includes(), Array.indexOf(), Array.find()

- C++: std::vector::find(), manuell loop med ==-jämförelse

- Go: slices.Contains(), manuell loop över en slice

Fixen i varje fall: ersätt den sekventiella containern med en hash-container. listset. ArrayListHashSet. ArraySet. vectorunordered_set. slicemap[T]struct{}.

Ett nyckelord. En substitution. Noll beteendeförändring. 1 000× snabbare vid N=1 000.

Granska ett kodfragment

Tillämpa MOAD-0001-mönsterigenkänning på ett riktigt kodfragment.

Du granskar en JavaScript-kodbas och hittar detta inuti en loop som körs över alla grannar i en graf: `if (!openSet.includes(neighbor)) openSet.push(neighbor)`. Är detta MOAD-0001? Vad skulle du ersätta `openSet` med, och hur ändras komplexiteten från O(?) till O(?)?

En Rad, Fyra Språk

Fixen för MOAD-0001 i fyra språk:

# Python
visited = []       # FÖRE: O(N) medlemskap
visited = set()    # EFTER:  O(1) membership
// Java
List<Node> visited = new ArrayList<>();    // FÖRE
Set<Node> visited = new HashSet<>();       // EFTER
// JavaScript
const visited = [];      // FÖRE: Array.includes() O(N)
const visited = new Set(); // EFTER: Set.has() O(1)
// visited.push(n) → visited.add(n)
// visited.includes(n) → visited.has(n)
// Go
visited := []NodeID{}              // FÖRE: slices.Contains() O(N)
visited := make(map[NodeID]struct{}) // EFTER: map lookup O(1)
// _, ok := visited[n]  för medlemskoll
// visited[n] = struct{}{}  för insättning

I varje fall: samma beteende, samma utdata, samma tester som passerar. Endast tillväxttakten ändras.

Vad fixen INTE ändrar

- Algoritmens logiska beteende: depth-first besöker fortfarande depth-first

- Korrekt utdata: samma noder besöks i samma ordning (inom DFS)

- Testresultat: alla tester som kontrollerar korrekthet fortsätter att passera

Vad fixen ÄNDRAR

- Medlemskapstest: O(N) → O(1)

- Total tid för loop: O(N²) → O(N)

- Vid N=1 000: 1 000× snabbare

- Vid N=10 000: 10 000× snabbare

En begränsning: Ordning

Hash-containrar (set, HashSet, unordered_set) bevarar inte insättningsordningen. I Python 3.7+ bevarar dict insättningsordningen; vanlig set gör det inte. I Java bevarar HashSet inte ordningen; LinkedHashSet gör det.

Om ordning är viktig tillsammans med medlemskap: underhåll två strukturer. Ett set (eller HashSet) för O(1)-medlemskapstester. En separat list (eller ArrayList) för ordnad genomgång. Eller använd LinkedHashSet i Java, som ger båda.

För MOAD-0001 i grafgenomgång: visited besvarar en binär fråga (har denna nod setts tidigare?). Ordning spelar ingen roll för medlemskapsfrågan. Genomgångsordningen kommer från stacken eller kön, inte från visited.

Medlemskap vs ordning

I Tarjans algoritm för starkt sammanhängande komponenter spårar onStack vilka noder som fortfarande finns på den aktuella DFS-anropsstacken. Den måste besvara två frågor: (1) medlemskap — finns denna nod för närvarande på stacken? (2) i slutet av en DFS-väg, poppa noder i ordning tills denna nod dyker upp.

En naiv implementation använder en List för onStack och besvarar medlemskapsfrågan med contains() — O(N) per kontroll, O(N²) totalt för stora grafer.

Du fixar en Tarjan SCC-implementation genom att ersätta `onStack = []` med `onStack = set()`. Testerna går igenom. En kodgranskare frågar: ”men tänk om ordning spelar roll för onStack?” Hur svarar du?

Varför detta är en disclosure, inte en patch

MOAD-0001 finns i över 1 000 verifierade platser i 60+ mjukvaruekosystem. Graftraversering i byggverktyg, ruttavdragning i nätverksroutrar, kollisionsdetektering i spelmotorer, resursallokering i operativsystemets schemaläggare.

Varje plats: korrekt kod. Varje plats: O(N²)-tillväxt som väntar på att N ska passera en tröskel.

Disclosure-pipeline

En patch utan upstream-disclosure fixar bara en plats. Mönstret återkommer i nästa version, nästa bibliotek, nästa språkportning. Disclosure: namnge mönstret, dokumentera det som CWE-407 (Algorithmic Complexity: Inefficient Algorithmic Complexity), publicera igenkänningsheuristiken och den enkla fixen. Då kan varje utvecklare som läser disclosuren känna igen och åtgärda sitt eget fall.

Hamming kallade detta ”ge en fisk vs lära ut fiske”. Patchen ger en fisk. Disclosuren — MOAD-0001 namngiven, mönstret dokumenterat, fixen generaliserad — lär ut heuristiken.

Permadatorns utvidgning

Hamming arbetade med punktlösningar: fixa detta filter, förbättra denna kod. Avhamming lägger till avslöjandelagret: namnge mönstret, publicera heuristiken och ge den till allmänningen.

Principen om sammansatt kunskap gäller här på ekosystemnivå. En forskare namnger ett mönster. Hundra utvecklare känner igen det i sina egna kodbaser. Tusen rader O(N²)-kod blir O(N). Infrastrukturen blir snabbare för alla som bygger på den.

Detta är draken som växer: samma eld (arbeta med viktiga problem, sammansatt kunskap, systemtänkande), annan flygning (öppet avslöjande, gemensamt ägande, ingen mecenat krävs).