arrayfun อาจช้ากว่าการวนซ้ำอย่างชัดเจนใน matlab ทำไม?


105

พิจารณาการทดสอบความเร็วอย่างง่ายต่อไปนี้สำหรับarrayfun:

T = 4000;
N = 500;
x = randn(T, N);
Func1 = @(a) (3*a^2 + 2*a - 1);

tic
Soln1 = ones(T, N);
for t = 1:T
    for n = 1:N
        Soln1(t, n) = Func1(x(t, n));
    end
end
toc

tic
Soln2 = arrayfun(Func1, x);
toc

บนเครื่องของฉัน (Matlab 2011b บน Linux Mint 12) ผลลัพธ์ของการทดสอบนี้คือ:

Elapsed time is 1.020689 seconds.
Elapsed time is 9.248388 seconds.

อะไรวะ!? arrayfunในขณะที่เป็นที่ยอมรับวิธีการแก้ปัญหาที่ดูสะอาดตาเป็นลำดับขนาดที่ช้าลง เกิดขึ้นที่นี่คืออะไร?

นอกจากนี้ฉันได้ทำการทดสอบรูปแบบคล้าย ๆ กันcellfunและพบว่ามันช้ากว่าการวนซ้ำอย่างชัดเจนประมาณ 3 เท่า อีกครั้งผลลัพธ์นี้ตรงกันข้ามกับที่ฉันคาดไว้

คำถามของฉันคือทำไมarrayfunและcellfunมากช้าลง? และด้วยเหตุนี้มีเหตุผลที่ดีที่จะใช้ (นอกเหนือจากการทำให้รหัสดูดี) หรือไม่?

หมายเหตุ:ฉันกำลังพูดถึงเวอร์ชันมาตรฐานarrayfunที่นี่ไม่ใช่เวอร์ชัน GPU จากกล่องเครื่องมือการประมวลผลแบบขนาน

แก้ไข:เพื่อความชัดเจนฉันทราบว่าFunc1ด้านบนสามารถเป็นเวกเตอร์ได้ตามที่ Oli ชี้ไว้ ฉันเลือกเพียงเพราะให้ผลการทดสอบความเร็วอย่างง่ายสำหรับวัตถุประสงค์ของคำถามจริง

แก้ไข:ตามคำแนะนำของ grungetta ฉันทำการทดสอบอีกครั้งด้วยfeature accel off. ผลลัพธ์คือ:

Elapsed time is 28.183422 seconds.
Elapsed time is 23.525251 seconds.

กล่าวอีกนัยหนึ่งดูเหมือนว่าส่วนสำคัญของความแตกต่างก็คือตัวเร่งความเร็ว JIT ทำงานได้ดีกว่าในการเร่งความเร็วforลูปที่ชัดเจนกว่าที่เป็นarrayfunอยู่ สิ่งนี้ดูแปลกสำหรับฉันเนื่องจากarrayfunให้ข้อมูลมากกว่านั้นจริง ๆ เช่นการใช้งานพบว่าลำดับของการโทรที่Func1จะไม่สำคัญ นอกจากนี้ฉันสังเกตว่าไม่ว่าจะเปิดหรือปิดตัวเร่ง JIT ระบบของฉันจะใช้ CPU เพียงตัวเดียว ...


10
โชคดีที่ "โซลูชันมาตรฐาน" ยังคงเร็วที่สุด: tic; 3 * x. ^ 2 + 2 * x-1; toc เวลาที่ผ่านไปคือ 0.030662 วินาที
Oli

4
@Oli ฉันคิดว่าฉันควรจะคาดหวังว่าจะมีคนชี้เรื่องนี้และใช้ฟังก์ชันที่ไม่สามารถเป็นเวกเตอร์ได้ :-)
Colin T Bowers

3
ฉันสนใจที่จะดูว่าเวลานี้เปลี่ยนไปอย่างไรเมื่อปิดตัวเร่ง JIT ดำเนินการคำสั่ง 'feature accel off' แล้วรันการทดสอบของคุณอีกครั้ง
grungetta

@grungetta ข้อเสนอแนะที่น่าสนใจ ฉันได้เพิ่มผลลัพธ์ให้กับคำถามพร้อมกับความคิดเห็นเล็กน้อย
Colin T Bowers

คำตอบ:


101

คุณสามารถรับแนวคิดได้โดยการเรียกใช้โค้ดเวอร์ชันอื่น ๆ พิจารณาเขียนการคำนวณอย่างชัดเจนแทนที่จะใช้ฟังก์ชันในลูปของคุณ

tic
Soln3 = ones(T, N);
for t = 1:T
    for n = 1:N
        Soln3(t, n) = 3*x(t, n)^2 + 2*x(t, n) - 1;
    end
end
toc

เวลาคำนวณบนคอมพิวเตอร์ของฉัน:

Soln1  1.158446 seconds.
Soln2  10.392475 seconds.
Soln3  0.239023 seconds.
Oli    0.010672 seconds.

ตอนนี้ในขณะที่การแก้ปัญหาอย่างเต็มที่ 'vectorized' เป็นอย่างชัดเจนที่เร็วที่สุดที่คุณจะเห็นว่าการกำหนดฟังก์ชั่นที่จะเรียกว่ารายการ x ทุกคนเป็นอย่างมากค่าใช้จ่าย เพียงแค่เขียนการคำนวณอย่างชัดเจนก็ทำให้เราได้รับปัจจัยที่ 5 เร่งความเร็ว ผมคิดว่าการแสดงนี้ว่า MATLABs JIT คอมไพเลอร์ไม่สนับสนุนฟังก์ชั่นแบบอินไลน์ ตามคำตอบของ gnovice ที่นั่นการเขียนฟังก์ชันปกติจะดีกว่าการเขียนแบบไม่ระบุตัวตน ลองมัน.

ขั้นตอนต่อไป - ลบ (vectorize) วงใน:

tic
Soln4 = ones(T, N);
for t = 1:T
    Soln4(t, :) = 3*x(t, :).^2 + 2*x(t, :) - 1;
end
toc

Soln4  0.053926 seconds.

การเร่งความเร็วปัจจัยที่ 5 อีกประการหนึ่ง: มีบางอย่างในข้อความเหล่านี้ที่บอกว่าคุณควรหลีกเลี่ยงการวนซ้ำใน MATLAB ... หรือมีจริง? ลองดูที่นี่แล้ว

tic
Soln5 = ones(T, N);
for n = 1:N
    Soln5(:, n) = 3*x(:, n).^2 + 2*x(:, n) - 1;
end
toc

Soln5   0.013875 seconds.

ใกล้เคียงกับเวอร์ชัน vectorized 'เต็ม' มากขึ้น Matlab จัดเก็บเมทริกซ์คอลัมน์ที่ชาญฉลาด คุณควรจัดโครงสร้างการคำนวณของคุณ (ถ้าเป็นไปได้) ให้เป็นเวกเตอร์ 'คอลัมน์ที่ชาญฉลาด'

เราสามารถกลับไปที่ Soln3 ได้แล้ว ลำดับการวนซ้ำคือ 'row-wise' มาเปลี่ยนกันเถอะ

tic
Soln6 = ones(T, N);
for n = 1:N
    for t = 1:T
        Soln6(t, n) = 3*x(t, n)^2 + 2*x(t, n) - 1;
    end
end
toc

Soln6  0.201661 seconds.

ดีขึ้น แต่ก็ยังแย่มาก ห่วงเดียว - ดี ห่วงคู่ - ไม่ดี ฉันเดาว่า MATLAB ทำงานได้ดีในการปรับปรุงประสิทธิภาพของลูป แต่ยังคงมีค่าใช้จ่ายของลูปอยู่ที่นั่น หากคุณมีงานที่หนักกว่าอยู่ข้างในคุณจะไม่สังเกตเห็น แต่เนื่องจากการคำนวณนี้มีการ จำกัด แบนด์วิธของหน่วยความจำคุณจึงเห็นค่าใช้จ่ายแบบวนซ้ำ และคุณจะเห็นค่าใช้จ่ายในการโทร Func1 ที่นั่นได้ชัดเจนยิ่งขึ้น

แล้ว arrayfun เกิดอะไรขึ้น? ไม่มีฟังก์ชัน inlinig เช่นกันค่าใช้จ่ายจำนวนมาก แต่ทำไมแย่กว่าการวนซ้ำซ้อนกันมากนัก? อันที่จริงเรื่องของการใช้ cellfun / arrayfun ได้รับการกล่าวถึงอย่างกว้างขวางหลายครั้ง (เช่นที่นี่ , ที่นี่ , ที่นี่และที่นี่ ) ฟังก์ชันเหล่านี้ทำงานช้าคุณไม่สามารถใช้สำหรับการคำนวณแบบละเอียดเช่นนี้ได้ คุณสามารถใช้มันเพื่อความกะทัดรัดของโค้ดและการแปลงแบบแฟนซีระหว่างเซลล์และอาร์เรย์ แต่ฟังก์ชันจะต้องหนักกว่าที่คุณเขียน:

tic
Soln7 = arrayfun(@(a)(3*x(:,a).^2 + 2*x(:,a) - 1), 1:N, 'UniformOutput', false);
toc

Soln7  0.016786 seconds.

โปรดทราบว่า Soln7 เป็นเซลล์ในขณะนี้ .. บางครั้งก็มีประโยชน์ ตอนนี้ประสิทธิภาพของโค้ดค่อนข้างดีและหากคุณต้องการเซลล์เป็นเอาต์พุตคุณไม่จำเป็นต้องแปลงเมทริกซ์ของคุณหลังจากที่คุณใช้โซลูชันแบบเวกเตอร์เต็มรูปแบบแล้ว

เหตุใด arrayfun จึงช้ากว่าโครงสร้างลูปธรรมดา น่าเสียดายที่เราไม่สามารถพูดได้อย่างแน่นอนเนื่องจากไม่มีซอร์สโค้ด คุณสามารถเดาได้ว่าเนื่องจาก arrayfun เป็นฟังก์ชันวัตถุประสงค์ทั่วไปซึ่งจัดการโครงสร้างข้อมูลและอาร์กิวเมนต์ที่แตกต่างกันทุกประเภทจึงไม่จำเป็นต้องเร็วมากในกรณีง่ายๆซึ่งคุณสามารถแสดงเป็นรังแบบวนซ้ำได้โดยตรง ค่าใช้จ่ายมาจากไหนเราไม่สามารถทราบได้ สามารถหลีกเลี่ยงค่าใช้จ่ายได้โดยการใช้งานที่ดีกว่าหรือไม่? อาจจะไม่. แต่น่าเสียดายที่สิ่งเดียวที่เราทำได้คือศึกษาประสิทธิภาพเพื่อระบุกรณีที่ใช้งานได้ดีและกรณีที่ทำไม่ได้

อัปเดตเนื่องจากเวลาดำเนินการของการทดสอบนี้สั้นเพื่อให้ได้ผลลัพธ์ที่เชื่อถือได้ฉันจึงเพิ่มการวนรอบการทดสอบ:

for i=1:1000
   % compute
end

บางครั้งระบุไว้ด้านล่าง:

Soln5   8.192912 seconds.
Soln7  13.419675 seconds.
Oli     8.089113 seconds.

คุณจะเห็นว่า arrayfun ยังคงแย่อยู่ แต่อย่างน้อยก็ไม่ใช่คำสั่งขนาดสามคำสั่งที่แย่กว่าโซลูชันเวกเตอร์ ในทางกลับกันลูปเดียวที่มีการคำนวณแบบคอลัมน์จะเร็วพอ ๆ กับเวอร์ชันเวคเตอร์ทั้งหมด ... ซึ่งทั้งหมดนี้ทำได้ใน CPU ตัวเดียว ผลลัพธ์ของ Soln5 และ Soln7 จะไม่เปลี่ยนแปลงหากฉันเปลี่ยนเป็น 2 คอร์ - ใน Soln5 ฉันจะต้องใช้พาร์ฟอร์เพื่อให้มันขนานกัน ลืมเรื่อง speedup ... Soln7 ไม่ทำงานแบบขนานเนื่องจาก arrayfun ไม่ทำงานแบบขนาน Olis vectorized version ในทางกลับกัน:

Oli  5.508085 seconds.

9
ตอบโจทย์มาก! และลิงก์ไปยัง matlab central ทั้งหมดให้การอ่านที่น่าสนใจมาก ขอบคุณมาก.
Colin T Bowers

นี่เป็นการวิเคราะห์ที่ดี
H.Muster

และอัพเดทที่น่าสนใจ! คำตอบนี้ยังคงให้ :-)
Colin T Bowers

3
เป็นเพียงความคิดเห็นเล็ก ๆ น้อย ๆ ย้อนกลับไปใน MATLAB 6.5 cellfunถูกนำไปใช้เป็นไฟล์ MEX (มีซอร์สโค้ด C อยู่ข้างๆ) มันค่อนข้างตรงไปตรงมา แน่นอนว่ารองรับการใช้ฟังก์ชันฮาร์ดโค้ดหนึ่งใน 6 ฟังก์ชันเท่านั้น (คุณไม่สามารถส่งแฮนเดิลฟังก์ชันได้มีเพียงสตริงที่มีชื่อฟังก์ชันเท่านั้น)
Amro

1
arrayfun + function handle = ช้า! หลีกเลี่ยงพวกเขาในรหัสหนัก
Yvon

-8

นั่นเพราะ !!!!

x = randn(T, N); 

ไม่ใช่gpuarrayประเภท;

สิ่งที่คุณต้องทำคือ

x = randn(T, N,'gpuArray');

2
ฉันคิดว่าคุณต้องอ่านคำถามและคำตอบที่ยอดเยี่ยมของ @angainor อย่างละเอียดอีกนิด gpuarrayมันไม่ได้มีอะไรจะทำอย่างไรกับ นั่นเป็นเหตุผลว่าทำไมคำตอบนี้จึงถูกลดลง
Colin T Bowers

@Colin - ฉันยอมรับว่า angainor มีความละเอียดรอบคอบมากกว่า แต่คำตอบไม่ได้กล่าวถึง 'gpuArray' ฉันคิดว่า 'gpuArray' เป็นส่วนสนับสนุนที่ดีที่นี่ (ถ้าถูกต้อง) นอกจากนี้คำถามยังเลอะเทอะเล็กน้อยว่า "เกิดอะไรขึ้นที่นี่" ดังนั้นฉันคิดว่ามันเปิดประตูสำหรับวิธีการเพิ่มเติมเช่น vectorizing data และจัดส่งไปยัง GPU ฉันปล่อยให้คำตอบนี้เกิดขึ้นเพราะสามารถเพิ่มมูลค่าให้กับผู้มาเยือนในอนาคตได้ ขออภัยหากโทรผิด
jww

1
คุณยังลืมข้อเท็จจริงที่gpuarrayรองรับเฉพาะสำหรับกราฟิกการ์ด nVidia หากพวกเขาไม่มีฮาร์ดแวร์ดังกล่าวคำแนะนำของคุณ (หรือขาด) ก็ไม่มีความหมาย -1
rayryeng

ในทางกลับกัน gpuarray เป็นกระบี่แสงของการเขียนโปรแกรม matlab vectorized
MrIO
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.