8

Betrachten Sie die folgende Funktion:Unnötige Zuweisungen mit Julia Update Operatoren

function mytest(x, b) 
    y = zeros(x[:,:,1]) 
    for i in 1:length(b) 
     y += b[i] * x[:,:,i] 
    end 
    return y 
end 

Wenn ich es laufen, erhalte ich folgendes:

x = rand(30,30,100000) 
b = rand(100000) 
@time mytest(x,b) 

elapsed time: 0.571765222 seconds (727837732 bytes allocated, 66.49% gc time) 

Warum ist es so viel Speicher und die Ausgaben so viel Zeit Zuteilung Garbage Collection machen? Der Code sollte type stable sein, und ich würde erwarten, dass der Operator += keine Neuzuweisung durchführt. Es scheint jedoch, dass es jedes Mal neu zugewiesen wird, wenn zwei Matrizen hinzugefügt werden.

Sollte ich das als Fehler in Julia betrachten? Und noch wichtiger, wie kann ich diesen Code so schreiben, dass er nicht neu zugeordnet wird?

EDIT: festen Tippfehler.

+1

Die rechte Seite 'b [i] * x [:,:, i]' weist zuerst ein temporäres Array dem Ergebnis des Produkts zu, das vor Ort auf der linken Seite hinzugefügt wird. Das temporäre Array muss dann als Müll gesammelt werden. Zumindest ... so würde es mit [tag: numpy] funktionieren. –

+3

@moarningsun, danke für den Tipp. Meine vorherige Erfahrung mit leistungskritischem Code verwendet hauptsächlich C++ und Eigen, die bei der Aufnahme eines Slices kein temporäres zuweisen würden. Ich habe meinen Code geändert, um drei verschachtelte for-Schleifen zu haben, und das Zuordnungsproblem wurde behoben. (Vorher habe ich versucht, das ArrayViews-Paket zu verwenden, aber das schien die Dinge nicht zu reparieren.) Ich bin immer noch neugierig, ob es einen besseren Weg gibt. –

+1

@JimGarrison: Überprüfen Sie die Diskussion [hier] (https://groups.google.com/forum/#!topic/julia-users/i5hfGpWRHlk). '+ =' ist nur syntaktischer Zucker (für jetzt zumindest) und so denke ich, dass es immer nur neu zuweist (aber jemand korrigiert mich bitte, wenn ich falsch liege). Außerdem wäre ich daran interessiert zu sehen, wie die Three Loop Ihr ​​Problem gelöst hat. – cd98

Antwort

4

@ cd98 hat meine Three-Nested-Loop-Lösung angefordert, die das Zuordnungsproblem löst, aber ich nehme an, dass sie eine äquivalente vektorisierte Version unterlaufen würde. Hier ist sie:

function mytest(x, b) 
    d1, d2, d3 = size(x) 
    y = zeros(eltype(x), d1, d2) 
    for i in 1:d3 
     for j in 1:d2 
      for k in 1:d1 
       y[k,j] += b[i] * x[k,j,i] 
      end 
     end 
    end 
    return y 
end 

x = rand(30,30,100000) 
b = rand(100000) 
@time mytest(x,b) 
@time mytest(x,b) 

Und die Ausgabe:

elapsed time: 0.218220119 seconds (767172 bytes allocated) 
elapsed time: 0.197181799 seconds (7400 bytes allocated) 
3

Es besteht auch die Möglichkeit Base.Cartesian der Verwendung: nach using Base.Cartesian, können Sie schreiben

@nloops 3 i x begin 
    (@nref 2 y i) += b[i_3] * (@nref 3 x i) 
end 

welche expandiert nach im Grunde das gleiche Loops wie in Jims Antwort.

+1

Aber persönlich denke ich, das ist hässlich und ich bevorzuge Jims Lösung. – user4235730

4

Hat das (original) Zuordnungsproblem nicht lösen, aber ich habe über einen 1.8x Speedup, indem einfach die Schleifen in der zweiten Lösung mit @inbounds Verpackung:

function mytest_inbounds(x, b) 
    d1, d2, d3 = size(x) 
    y = zeros(eltype(x), d1, d2) 
    @inbounds begin 
     for i in 1:d3 
      for j in 1:d2 
       for k in 1:d1 
        y[k,j] += b[i] * x[k,j,i] 
       end 
      end 
     end 
    end 
    return y 
end 

x = rand(30, 30, 100000) 
b = rand(100000) 
@time mytest(x, b) 
@time mytest(x, b) 
@time mytest_inbounds(x, b) 
@time mytest_inbounds(x, b) 

Ausgang:

elapsed time: 0.39144919 seconds (767212 bytes allocated) 
elapsed time: 0.353495867 seconds (7400 bytes allocated) 
elapsed time: 0.202614643 seconds (396972 bytes allocated) 
elapsed time: 0.193425902 seconds (7400 bytes allocated) 

auch hier viele gute Diskussion zu verwandten Themen:

https://groups.google.com/forum/#!msg/julia-users/aYS_AvKqPCI/DyTiq4lKIAoJ