2010-07-15 32 views
14

Tôi đã viết một trình phân tích tệp trong Perl, vì vậy phải lặp qua tệp. Tệp bao gồm các bản ghi độ dài cố định và tôi muốn tạo một hàm riêng biệt phân tích bản ghi đã cho và gọi hàm đó trong một vòng lặp. Tuy nhiên, kết quả cuối cùng đã trở nên chậm chạp với các tệp lớn và dự đoán của tôi là tôi không nên sử dụng chức năng bên ngoài. Vì vậy, tôi đã làm một số xét nghiệm giả có và không có lời gọi hàm trong một vòng lặp:tại sao các cuộc gọi hàm trong vòng lặp Perl quá chậm?

[A]

foreach (1 .. 10000000) { 
$a = &get_string(); 
} 

sub get_string { 
return sprintf("%s\n", 'abc'); 
} 

[B]

foreach (1 .. 10000000) { 
$a = sprintf "%s\n", 'abc'; 
} 

đo cho thấy Mã chạy khoảng 3-4 lần chậm hơn mã B. Tôi biết trước rằng mã A được cho là chạy chậm hơn nhưng tôi vẫn ngạc nhiên rằng sự khác biệt là lớn. Cũng cố gắng chạy thử nghiệm tương tự với Python và Java. Trong mã Python Một tương đương chậm hơn khoảng 20% ​​so với mã B và Java được chạy nhiều hơn hoặc ít hơn ở cùng tốc độ (như mong đợi). Thay đổi chức năng từ sprintf sang một cái gì đó khác đã không hiển thị bất kỳ sự khác biệt đáng kể.

Có cách nào giúp Perl chạy vòng lặp nhanh hơn không? Tôi đang làm một cái gì đó totaly sai ở đây hoặc là tính năng của Perl rằng các cuộc gọi chức năng là chi phí như vậy?

+0

gì, chính xác, không get_string() làm gì? – eruciform

+1

@roe Chúng tôi giả định rằng đó là một sơ khai và bạn đã không sử dụng 'sprintf' chỉ để dính một dòng mới vào một chuỗi liên tục. Đó sẽ là ngớ ngẩn. Vậy nó thực sự làm gì? – Schwern

+0

lạ, màn hình của tôi được định dạng kỳ lạ, nó không ở trên đó trước đây. firefox goof .. – eruciform

Trả lời

8

Vấn đề bạn đang gây ra không có liên quan gì đến vòng lặp. Cả hai ví dụ AB đều giống nhau về vấn đề đó. Thay vào đó, vấn đề là sự khác biệt giữa mã hóa trực tiếp, trực tuyến so với việc gọi cùng một mã thông qua một hàm.

Cuộc gọi chức năng liên quan đến chi phí không thể tránh khỏi. Tôi không thể nói về vấn đề này và tại sao chi phí này đắt hơn Perl so với các ngôn ngữ khác, nhưng tôi có thể minh họa cách tốt hơn để đo lường loại điều này:

use strict; 
use warnings; 
use Benchmark qw(cmpthese); 

sub just_return { return } 
sub get_string { my $s = sprintf "%s\n", 'abc' } 

my %methods = (
    direct  => sub { my $s = sprintf "%s\n", 'abc' }, 
    function => sub { my $s = get_string()   }, 
    just_return => sub { my $s = just_return()   }, 
); 

cmpthese(-2, \%methods); 

Đây là những gì tôi nhận được trên Perl v5.10.0 (MSWin32-x86-multi-thread). Rất gần, chỉ cần gọi một chức năng mà không có gì là tốn kém như trực tiếp chạy mã sprintf của chúng tôi.

    Rate function just_return  direct 
function 1062833/s   --  -70%  -71% 
just_return 3566639/s  236%   --   -2% 
direct  3629492/s  241%   2%   -- 

Nói chung, nếu bạn cần để tối ưu hóa một số mã Perl cho tốc độ và bạn đang cố gắng bóp ra từng giọt cuối cùng của hiệu quả, mã hóa trực tiếp là con đường để đi - nhưng điều đó thường đi kèm với một mức giá ít bảo trì và dễ đọc hơn. Tuy nhiên, trước khi bạn bắt đầu kinh doanh tối ưu hóa vi mô như vậy, bạn muốn đảm bảo rằng thuật toán cơ bản của bạn là vững chắc và bạn nắm vững vị trí của các phần chậm của mã thực sự. Thật dễ dàng để lãng phí rất nhiều nỗ lực làm việc trên những điều sai trái.

+0

Tôi chỉ nhận được 1 hoặc 2% sự khác biệt giữa hàm và hàm với proto. Perl 5.10/windows XP và Perl 5.8.5 i386/Linux 2.6.12 i386 và Perl 5.8.8 x86_64/Linux 2.6.18 x86_64. – Toto

+0

@ M42 Tôi nghĩ bạn muốn bình luận về câu trả lời của Dolmen. – FMc

+0

@FM: Vâng, tất nhiên rồi. – Toto

12

Nếu tiểu của bạn không có lý lẽ và là một hằng số như trong ví dụ của bạn, bạn có thể có được một chính tốc độ lên bằng cách sử dụng an empty prototype "()" trong khai báo phụ:

sub get_string() { 
    return sprintf(“%s\n”, ‘abc’); 
} 

Tuy nhiên điều này có lẽ là một trường hợp đặc biệt cho ví dụ của bạn không khớp với trường hợp thực của bạn. Đây chỉ là để cho bạn thấy sự nguy hiểm của điểm chuẩn.

Bạn sẽ tìm hiểu mẹo này và nhiều mẹo khác bằng cách đọc perlsub.

Dưới đây là một chuẩn mực:

use strict; 
use warnings; 
use Benchmark qw(cmpthese); 

sub just_return { return } 
sub get_string { sprintf "%s\n", 'abc' } 
sub get_string_with_proto() { sprintf "%s\n", 'abc' } 

my %methods = (
    direct  => sub { my $s = sprintf "%s\n", 'abc' }, 
    function => sub { my $s = get_string()   }, 
    just_return => sub { my $s = just_return()   }, 
    function_with_proto => sub { my $s = get_string_with_proto() }, 
); 

cmpthese(-2, \%methods); 

và kết quả của nó:

      Rate function just_return direct function_with_proto 
function    1488987/s  --  -65%  -90%    -90% 
just_return   4285454/s  188%   --  -70%    -71% 
direct    14210565/s  854%  232%  --     -5% 
function_with_proto 15018312/s  909%  250%  6%     -- 
+3

Thư mục liên tục dường như đã thông minh hơn trong khoảng từ 5.10.0 đến 5.10.1. Nó từng là Perl chỉ có thể liên tục gấp những biểu thức rất đơn giản. 5.10.1 giờ đây có thể xử lý những thứ phức tạp hơn như cuộc gọi chạy nước rút. – Schwern

+0

Điểm chuẩn của tôi là trên StrawberryPerl 5.12.0.1. – dolmen

23

gọi hàm Perl là chậm. Nó hút bởi vì điều bạn muốn làm, phân tách mã của bạn thành các chức năng có thể duy trì, là điều rất sẽ làm chậm chương trình của bạn. Tại sao chúng lại chậm? Perl thực hiện rất nhiều thứ khi nó đi vào một chương trình con, kết quả của nó là cực kỳ năng động (ví dụ, bạn có thể gây rối với rất nhiều thứ trong thời gian chạy). Nó phải lấy mã tham chiếu cho tên đó, kiểm tra xem đó có phải là mã ref hay không, thiết lập một scratchpad từ vựng mới (để lưu trữ các biến số my), phạm vi động mới (để lưu local biến), thiết lập @_ , kiểm tra ngữ cảnh nào được gọi và chuyển cùng giá trị trả về. Những nỗ lực đã được thực hiện để tối ưu hóa quá trình này, nhưng họ đã không trả tiền. Xem pp_entersub in pp_hot.c để biết chi tiết đẫm máu.

Cũng có lỗi trong các chức năng làm chậm 5.10.0. Nếu bạn đang sử dụng 5.10.0, hãy nâng cấp.

Kết quả là, tránh lặp lại các chức năng gọi điện trong một vòng lặp dài. Đặc biệt là nếu lồng nhau của nó. Bạn có thể lưu lại kết quả, có lẽ sử dụng Memoize? Công việc phải được thực hiện bên trong vòng lặp? Liệu nó có phải được thực hiện bên trong vòng lặp bên trong nhất? Ví dụ:

for my $thing (@things) { 
    for my $person (@persons) { 
     print header($thing); 
     print message_for($person); 
    } 
} 

Các cuộc gọi đến header có thể được di chuyển ra khỏi vòng lặp @persons giảm số lượng các cuộc gọi từ @things * @persons chỉ @things.

for my $thing (@things) { 
    my $header = header($thing); 

    for my $person (@persons) { 
     print $header; 
     print message_for($person); 
    } 
} 
1

Trình tối ưu hóa perl là liên tục gấp sprintf cuộc gọi trong mã mẫu của bạn.

Bạn có thể deparse nó để xem nó xảy ra:

$ perl -MO=Deparse sample.pl 
foreach $_ (1 .. 10000000) { 
    $a = &get_string(); 
} 
sub get_string { 
    return "abc\n"; 
} 
foreach $_ (1 .. 10000000) { 
    $a = "abc\n"; 
} 
- syntax OK 
Các vấn đề liên quan