Ich suche nach einem Beispiel, bei dem ein Algorithmus anscheinend seine Komplexitätsklasse aufgrund von Compiler- und / oder Prozessoroptimierungsstrategien ändert.
int main(void) { exit(0); };
Ich suche nach einem Beispiel, bei dem ein Algorithmus anscheinend seine Komplexitätsklasse aufgrund von Compiler- und / oder Prozessoroptimierungsstrategien ändert.
int main(void) { exit(0); };
Antworten:
Nehmen wir ein einfaches Programm, das das Quadrat einer in der Befehlszeile eingegebenen Zahl druckt.
#include <stdio.h>
int main(int argc, char **argv) {
int num = atoi(argv[1]);
printf("%d\n",num);
int i = 0;
int total = 0;
for(i = 0; i < num; i++) {
total += num;
}
printf("%d\n",total);
return 0;
}
Wie Sie sehen können, handelt es sich um eine O (n) -Berechnung, die immer wieder wiederholt wird.
Wenn Sie dies mit gcc -S
einem kompilieren, erhalten Sie ein Segment, das ist:
LBB1_1:
movl -36(%rbp), %eax
movl -28(%rbp), %ecx
addl %ecx, %eax
movl %eax, -36(%rbp)
movl -32(%rbp), %eax
addl $1, %eax
movl %eax, -32(%rbp)
LBB1_2:
movl -32(%rbp), %eax
movl -28(%rbp), %ecx
cmpl %ecx, %eax
jl LBB1_1
Hier sehen Sie, wie das Hinzufügen durchgeführt wird, ein Vergleich und ein Rücksprung für die Schleife.
Durchführen der Kompilierung mit gcc -S -O3
, um Optimierungen des Segments zwischen den Aufrufen von printf zu erhalten:
callq _printf
testl %ebx, %ebx
jg LBB1_2
xorl %ebx, %ebx
jmp LBB1_3
LBB1_2:
imull %ebx, %ebx
LBB1_3:
movl %ebx, %esi
leaq L_.str(%rip), %rdi
xorb %al, %al
callq _printf
Man kann jetzt sehen, stattdessen hat es keine Schleife und außerdem keine Adds. Stattdessen gibt es einen Anruf, bei imull
dem die Zahl mit sich selbst multipliziert wird.
Der Compiler hat die Schleife und den darin enthaltenen mathematischen Operator erkannt und durch die richtige Berechnung ersetzt.
Beachten Sie, dass dies einen Anruf atoi
zum Abrufen der Nummer beinhaltete. Wenn die Nummer bereits im Code vorhanden ist, berechnet der Complier den Wert vor, anstatt tatsächliche Anrufe zu tätigen, wie in einem Vergleich zwischen der Leistung von sqrt in C # und C gezeigt, bei der sqrt(2)
(eine Konstante) 1.000.000 Mal über eine Schleife summiert wurde.
Die Tail Call-Optimierung kann die Speicherplatzkomplexität verringern. Ohne TCO weist diese rekursive Implementierung einer while
Schleife beispielsweise eine Raumkomplexität im ungünstigsten Fall auf Ο(#iterations)
, während sie mit TCO eine Raumkomplexität im ungünstigsten Fall aufweist Ο(1)
:
// This is Scala, but it works the same way in every other language.
def loop(cond: => Boolean)(body: => Unit): Unit = if (cond) { body; loop(cond)(body) }
var i = 0
loop { i < 3 } { i += 1; println(i) }
// 1
// 2
// 3
// E.g. ECMAScript:
function loop(cond, body) {
if (cond()) { body(); loop(cond, body); };
};
var i = 0;
loop(function { return i < 3; }, function { i++; print(i); });
Dies erfordert nicht einmal allgemeine Gesamtbetriebskosten, sondern nur einen sehr engen Sonderfall, nämlich die Beseitigung der direkten Schwanzrekursion.
Sehr interessant wäre jedoch, wenn eine Compileroptimierung nicht nur die Komplexitätsklasse ändert, sondern den Algorithmus tatsächlich vollständig ändert.
Der Glorious Glasgow Haskell Compiler macht das manchmal, aber das ist nicht wirklich das, worüber ich spreche, das ist eher wie Betrug. GHC verfügt über eine einfache Pattern Matching Language, mit der der Entwickler der Bibliothek einige einfache Codemuster erkennen und durch anderen Code ersetzen kann. Und die GHC Umsetzung der Haskell - Standardbibliothek hat einige dieser Anmerkungen zu enthalten, so dass bestimmte Verwendungen von spezifischen Funktionen , die ineffizient sind dafür bekannt, neu geschrieben in effizientere Versionen.
Diese Übersetzungen werden jedoch von Menschen geschrieben und sind für bestimmte Fälle geschrieben. Deshalb halte ich das für Betrug.
Ein Supercompiler kann den Algorithmus möglicherweise ohne menschliche Eingabe ändern, aber AFAIK hat noch nie einen Supercompiler auf Produktionsebene erstellt.
Ein Compiler, der sich bewusst ist, dass die Sprache eine Reduzierung der Stärke durch große Zahlen verwendet (Multiplikationen durch den Index einer Schleife durch eine Addition ersetzen), würde die Komplexität dieser Multiplikation von O (n log n) bestenfalls auf O (n) ändern. .