Ist die Indizierung von Vektoren in MATLAB ineffizient?

Hintergrund

Meine Frage ist durch einfache Beobachtungen motiviert, die die Überzeugungen / Annahmen, die erfahrene MATLAB-Benutzer häufig vertreten / vertreten, etwas untergraben:

MATLAB ist sehr gut optimiert, wenn es um die eingebauten Funktionen und die grundlegenden Sprachmerkmale wie die Indizierung von Vektoren und Matrizen geht.MATLAB-Schleifen sind (trotz JIT) langsam und sollten generell vermieden werden, wenn der Algorithmus auf native, "vektorisierte" Weise ausgedrückt werden kann.

Fazit: Die Kernfunktionalität von MATLAB ist effizient und der Versuch, sie mit MATLAB-Code zu übertreffen, ist schwierig, wenn nicht unmöglich.

Untersuchung der Leistung der Vektorindizierung

Die folgenden Beispielcodes sind so grundlegend wie möglich: Ich weise allen Vektoreinträgen einen Skalarwert zu. Zuerst ordne ich einen leeren Vektor zux:

tic; x = zeros(1e8,1); toc
Elapsed time is 0.260525 seconds.

Habenx Ich möchte alle Einträge auf den gleichen Wert setzen. In der Praxis würden Sie es anders machen, z.x = value*ones(1e8,1)Hier geht es jedoch darum, die Leistung der Vektorindizierung zu untersuchen. Der einfachste Weg ist zu schreiben:

tic; x(:) = 1; toc
Elapsed time is 0.094316 seconds.

Nennen wir es Methode 1 (vom zugewiesenen Wert bisx). Es scheint sehr schnell zu sein (zumindest schneller als die Speicherzuweisung). Da ich hier nur den Arbeitsspeicher bearbeite, kann ich die Effizienz dieses Codes durch Berechnen des erhaltenen Codes abschätzeneffektive Speicherbandbreite und vergleiche es mit demHardware-Speicherbandbreite von meinem Computer:

eff_bandwidth = numel(x) * 8 bytes per double * 2 / time

Oben multipliziere ich mit2 Wenn kein SSE-Streaming verwendet wird, muss zum Festlegen von Werten im Speicher der Vektor sowohl aus dem Speicher gelesen als auch in den Speicher geschrieben werden. Im obigen Beispiel:

eff_bandwidth(1) = 1e8*8*2/0.094316 = 17 Gb/s

STREAM-Benchmark-Speicherbandbreite meines Computers liegt bei 17,9 Gbit / s, also liefert MATLAB in diesem Fall nahezu Spitzenleistung! So weit, ist es gut.

Methode 1 eignet sich, wenn Sie festlegen möchtenalles Vektorelemente auf einen gewissen Wert. Aber wenn Sie auf alle Elemente zugreifen möchtenstep Einträge, müssen Sie die ersetzen: mit z.B.1:step:end. Nachfolgend finden Sie einen direkten Geschwindigkeitsvergleich mit Methode 1:

tic; x(1:end) = 2; toc
Elapsed time is 0.496476 seconds.

Obwohl Sie nicht erwarten würden, dass es sich anders verhält, ist Methode 2 eindeutig ein großes Problem: Faktor 5 Verlangsamung ohne Grund. Mein Verdacht ist, dass MATLAB in diesem Fall den Indexvektor explizit zuordnet (1:end). Dies wird in gewisser Weise durch die Verwendung einer expliziten Vektorgröße anstelle von bestätigtend:

tic; x(1:1e8) = 3; toc
Elapsed time is 0.482083 seconds.

Die Methoden 2 und 3 sind gleich schlecht.

Eine andere Möglichkeit besteht darin, explizit einen Indexvektor zu erstellenid und verwenden Sie es zu indizierenx. Auf diese Weise erhalten Sie die flexibelsten Indizierungsfunktionen. In unserem Fall:

tic;
id = 1:1e8; % colon(1,1e8);
x(id) = 4;
toc
Elapsed time is 1.208419 seconds.

Das ist wirklich etwas - 12-fache Verlangsamung im Vergleich zu Methode 1! Ich verstehe, dass es aufgrund des zusätzlichen Speichers, der für verwendet wird, schlechter als Methode 1 sein sollteid, aber warum ist es so viel schlimmer als die Methoden 2 und 3?

Versuchen wir es mit den Loops - so hoffnungslos es auch klingen mag.

tic;
for i=1:numel(x)
    x(i) = 5;
end
toc
Elapsed time is 0.788944 seconds.

Eine große Überraschung - ein Loop schlägt einenvectorized Methode 4, ist aber immer noch langsamer als Methode 1, 2 und 3. Es stellt sich heraus, dass Sie es in diesem speziellen Fall besser machen können:

tic;
for i=1:1e8
    x(i) = 6;
end
toc
Elapsed time is 0.321246 seconds.

Und das ist wahrscheinlich das bizarrste Ergebnis dieser Studie -Eine von MATLAB geschriebene Schleife übertrifft die native Vektorindizierung erheblich. Das sollte sicher nicht so sein. Beachten Sie, dass die JIT'ed-Schleife immer noch dreimal langsamer ist als der theoretische Peak, der mit Methode 1 fast erreicht wurde. Es gibt also noch viel Raum für Verbesserungen. Es ist nur überraschend (ein stärkeres Wort wäre geeigneter), dass die übliche "vektorisierte" Indizierung (1:end) ist noch langsamer.

Fragen

ist eine einfache Indizierung in MATLABsehr ineffizient (Methoden 2, 3 und 4 sind langsamer als Methode 1) oder habe ich etwas verpasst?Warum ist Methode 4(so viel) langsamer als Methoden 2 und 3?Warum macht mit1e8 anstattnumel(x) als Schleife gebunden den Code um den Faktor 2 beschleunigen?

Bearbeiten Nachdem Sie Jonas 'Kommentar gelesen haben, haben Sie hier eine andere Möglichkeit, dies mit logischen Indizes zu tun:

tic;
id = logical(ones(1, 1e8));
x(id) = 7;
toc
Elapsed time is 0.613363 seconds.

Viel besser als Methode 4.

Zur Bequemlichkeit:

function test

tic; x = zeros(1,1e8); toc

tic; x(:) = 1; toc
tic; x(1:end) = 2; toc
tic; x(1:1e8) = 3; toc

tic;
id = 1:1e8; % colon(1,1e8);
x(id) = 4;
toc

tic;
for i=1:numel(x)
    x(i) = 5;
end
toc

tic;
for i=1:1e8
    x(i) = 6;
end
toc

end

Antworten auf die Frage(2)

Ihre Antwort auf die Frage