2008-12-04 14 views
9

Tôi đã đánh giá hiệu suất của một framework mà tôi đang viết trong Perl và tôi nhận được yêu cầu giảm 50% mỗi giây so với codebase hiện tại của chúng tôi (một số lần truy cập là dễ hiểu, vì chúng tôi đang đi từ mã spaghetti theo thủ tục sang khung công tác OOP MVC).Làm cách nào để tôi có thể thực thi mã không hiệu quả chỉ khi biên dịch khi sử dụng mod_perl?

Ứng dụng đang chạy dưới mod_perl và tôi đã thêm Moose và tất cả mã khung của tôi vào số startup.pl script, tự tăng gấp đôi yêu cầu của tôi trên mỗi giây. Tôi đang tìm cách nâng cao hơn nữa con số này để có được nó càng gần càng tốt với số tiền hiện có. Lập luận là ở đây rằng đây là tối ưu hóa sớm, nhưng có một vài sự thiếu hiệu quả rõ ràng mà tôi muốn sửa chữa và xem nó ảnh hưởng như thế nào đến hiệu suất.

Giống như hầu hết các khung công tác, tôi có tệp cấu hình và người điều phối. Phần cấu hình được xử lý bởi Config::General, do đó, một chút IO và phân tích cú pháp có liên quan để tải tệp cấu hình của tôi vào ứng dụng. Vấn đề lớn nhất tôi thấy ở đây là tôi đang làm điều này cho MỌI YÊU CẦU mà đến!

Chạy Devel :: Dprof trên ứng dụng của tôi trỏ tới Config :: General :: BEGIN và một loạt các mô-đun IO có liên quan là một trong những điểm chậm chính không phải là Moose. Vì vậy, những gì tôi muốn làm, và những gì làm cho nhiều ý nghĩa hơn trong hindsight là tận dụng lợi thế của sự kiên trì mod_perl và các công cụ biên dịch startup.pl chỉ làm công việc để tải trong tập tin cấu hình một lần - khi máy chủ bắt đầu.

Vấn đề là tôi không quá quen thuộc với cách làm việc này.

Hiện nay mỗi dự án có một lớp bootstrapping PerlHandler mà là khá nạc và trông như thế này:

use MyApp; 
MyApp->new(config_file => '/path/to/site.config')->run(); 

MyApp.pm thừa hưởng từ các module Project khuôn khổ, trong đó có mã này:

my $config = Config::General->new(
       -ConfigFile => $self->config_file, 
       -InterPolateVars => 1, 
      );  

$self->config({$config->getall}); 

Để chỉ làm điều này vào thời gian biên dịch, cả hai mô đun cơ sở dự án và bootstrap của tôi sẽ phải thay đổi (tôi nghĩ), nhưng tôi không chắc chắn về những thay đổi để thực hiện và vẫn giữ mã đẹp và gọn gàng. Có ai có thể chỉ cho tôi đi đúng hướng không?

CẬP NHẬT

Tôi đã thử các BEGIN BLOCK trong mỗi phương pháp mô-đun dự án như mô tả của ysth trong câu trả lời của mình. Vì vậy, tôi bây giờ có:

package MyApp::bootstrap; 
use MyApp; 

my $config; 
BEGIN 
{ 
    $config = {Config::General->new(...)->getall};   
} 

sub handler { ..etc. 
    MyApp->new(config => $config)->run(); 

thay đổi nhanh chóng này mình đã cho tôi một tăng 50% yêu cầu mỗi giây, xác nhận suy nghĩ của tôi rằng tập tin cấu hình là một nút cổ chai giá trị sửa chữa lớn. Con số chuẩn trên máy dev cũ của chúng tôi là 60rps và khung của tôi đã tăng từ 30rps lên 45rps với sự thay đổi này một mình. Đối với những người nói Moose là chậm và có một thời gian biên dịch hit .. Tôi đã tăng cùng (50%) khi biên dịch tất cả các mã Moose của tôi lúc khởi động như tôi đã làm trước khi biên dịch tập tin cấu hình của tôi.

Vấn đề duy nhất tôi có bây giờ là vi phạm nguyên tắc DRY vì cùng một Config :: General-> mã mới nằm trong mọi khối BEGIN chỉ với đường dẫn đến tệp cấu hình khác nhau. Tôi có một vài chiến lược khác nhau để hạn chế điều này, nhưng tôi chỉ muốn đăng kết quả của sự thay đổi này.

Trả lời

10

Giả sử ứng dụng của bạn không thay đổi cấu hình gì cả, di chuyển nó vào một khối bắt đầu:

# this code goes at file scope 
my $config; 
BEGIN { 
    $config = { Config::General->new(...)->getall } 
} 

# when creating a new instance 
$self->config($config); 

Và chắc chắn rằng tất cả các module của bạn được biên soạn trong startup.pl.

Bạn có thể trở nên giàu có hơn và có một lớp đơn giản cung cấp băm cấu hình, nhưng bạn không cần.

+1

Vấn đề với giải pháp này là bạn phải tạo khối BEGIN này với mã đó cho mọi mô-đun dự án (mỗi dự án có tệp cấu hình riêng). Tôi đã thực hiện điều này một cách nhanh chóng và tôi nhận được yêu cầu tăng thêm 50% mỗi giây, vì vậy, tôi vẫn trả lời câu trả lời, bởi vì nó trả lời câu hỏi của tôi –

+1

Để xem "mỗi dự án cần cấu hình riêng", bạn có thể 1) Kết hợp tất cả các tệp thành 1 với các phần khác nhau (có thể sử dụng tệp INI hoặc hoặc Config :: ApacheFormat). 2) có một lớp cấu hình giữ mỗi tập tin cấu hình trong một băm và kéo một trong những quyền dựa trên một số $ ENV var. – mpeters

+1

Tôi chưa bao giờ nghĩ đến việc có một tệp cấu hình khổng lồ vì nó sẽ bảo trì như thế nào khi bạn có hàng trăm dự án ... nhưng thực sự có rất nhiều lợi ích khi thực hiện các kết nối cơ sở dữ liệu trên tất cả các dự án trong một số ít trường hợp. Cảm ơn. –

1

tôi đã có vấn đề tương tự trong một HTML :: khuôn khổ Mason cài đặt, và thấy điều này làm việc khá tốt: Trong httpd.conf:

PerlRequire handler.pl 
<FilesMatch "\.mhtml$"> 
    SetHandler perl-script 
    PerlHandler YourModule::Mason 
</FilesMatch> 

Trong file handler.pl của bạn, bạn xác định tất cả các mục tĩnh của bạn như cấu hình của bạn, xử lý cơ sở dữ liệu, vv Điều này định nghĩa chúng trong phạm vi YourModule :: Mason được biên dịch khi bắt đầu thread apache (chủ đề mới rõ ràng sẽ có một chi phí vốn có). YourModule :: Mason sau đó có phương thức handler xử lý yêu cầu.

Tôi sẽ thừa nhận rằng có thể có một số phép thuật đang xảy ra trong HTML :: Mason giúp tôi với điều này, nhưng nó phù hợp với tôi, có thể cho bạn?

+0

Xem câu trả lời của tôi bên dưới. Nó còn hơn cả việc tạo ra các vật thể mason của bạn ngay lập tức, bởi vì lúc đó bạn đã nạp một đoạn lớn Mason, mà không cần phải nạp lại, và có thể được chia sẻ một cách rõ ràng giữa các công nhân. –

4

Nếu bạn có thể tạo các lớp học Moose immutable, điều đó có thể khiến bạn tăng thêm tốc độ.

+1

Tất nhiên. Đây là một trong những điều đầu tiên bạn học được khi sử dụng Moose. –

+1

Có nhưng nó hữu ích cho mọi người biết ai không biết! (trong thực tế khi tôi lần đầu tiên thử Moose này thậm chí không có sẵn hoặc ít nhất là tài liệu). – draegtun

-2

JackM có ý tưởng đúng.

Bằng cách tải tất cả các lớp của bạn và khởi tạo đối tượng cấp ứng dụng (trong trường hợp của bạn, cấu hình) trong quy trình Apache "Mẹ", Bạn không phải biên dịch chúng mỗi lần một công nhân mới sinh sản, vì chúng đã có sẵn và trong bộ nhớ. Rất tỉ mỉ giữa chúng tôi thêm một dòng "sử dụng" cho mỗi mô-đun mà ứng dụng của họ sử dụng thường xuyên. Nếu bạn không tải các gói và mô-đun của bạn trong tàu mẹ, mỗi nhân viên không chỉ có hiệu suất truy cập tải các mô-đun, mà không thu được lợi ích của việc chia sẻ bộ nhớ mà các hệ điều hành hiện đại cung cấp.

Nó thực sự là một nửa khác của sự khác biệt giữa mod_perl và CGI. Với nửa đầu là động cơ perl-engine liên tục của mod_perl so với perl respawning của CGI cho mỗi lời gọi.

+0

Đọc câu hỏi? Tôi đã đề cập đến startup.pl nhiều lần. http://perl.apache.org/docs/2.0/user/config/config.html#C_PerlPostConfigRequire_ –

0

Một cách phổ biến của tăng tốc những điều đó với vài thay đổi chỉ đơn giản là sử dụng các biến toàn cầu và tình trạng bộ nhớ cache trong chúng giữa các lời gọi của cùng một quá trình Apache:

use vars qw ($config); 
# ... 
$config = Config::General->new(...)->getall 
    unless blessed($config); # add more suitable test here 

Nó không phải rất sạch sẽ và có thể dẫn đến khó hiểu lỗi (mặc dù "$ var của tôi" dẫn đến nhiều hơn trong kinh nghiệm của tôi) và đôi khi ăn rất nhiều bộ nhớ, nhưng nhiều (lặp đi lặp lại) báo cáo khởi tạo tốn kém có thể tránh được theo cách này. Lợi thế khi sử dụng BEGIN {}; chỉ có mã là bạn có thể khởi tạo lại dựa trên các sự kiện khác mà không cần phải khởi động lại apache hoặc giết chết quá trình của bạn (ví dụ: bằng cách bao gồm dấu thời gian của tệp trên đĩa trong thử nghiệm ở trên).

Coi chừng gotchas mặc dù: an easy way to break in

3

của một mô-đun import sub được thực hiện tại thời gian biên dịch, vì vậy chúng ta có thể sử dụng để giảm/loại bỏ DRY của ysth's answer.

Trong ví dụ sau, chúng tôi sử dụng phương thức nhập để đọc tệp cấu hình với các đối số đã cho chúng tôi và sau đó đẩy cấu hình đó vào gói gọi.

Thông báo trước là bất kỳ biến số nào trong gói gọi sẽ bị xóa bởi điều này.

package Foo_Config; 
use English qw(-no_match_vars); 
sub import { 
    my ($self, @cfg) = @ARG; 
    my $call_pkg  = caller; 
    my $config  = {Config::General->new(@cfg)->getall}; 
    do{ # this will create the $config variable in the calling package. 
     no strict 'refs'; 
     ${$call_pkg . '::config'} = $config; 
    }; 
    return; 
} 

package MyApp; 
# will execute Foo_Config->import('/path/to/site.config') at compile time. 
use Foo_Config '/path/to/site.config'; 
+0

+1, giải pháp của bạn giải quyết khiếu nại của OP sau khi cập nhật. –

Các vấn đề liên quan