Tất cả mọi thứ tôi đọc về các thực hành mã hóa PHP tốt hơn đều nói rằng đừng sử dụng require_once
vì tốc độ.
Tại sao lại thế này?
Cách thích hợp / tốt hơn để làm điều tương tự là require_once
gì? Nếu có vấn đề, tôi đang sử dụng PHP 5.
Tất cả mọi thứ tôi đọc về các thực hành mã hóa PHP tốt hơn đều nói rằng đừng sử dụng require_once
vì tốc độ.
Tại sao lại thế này?
Cách thích hợp / tốt hơn để làm điều tương tự là require_once
gì? Nếu có vấn đề, tôi đang sử dụng PHP 5.
Câu trả lời:
require_once
và include_once
cả hai đều yêu cầu hệ thống giữ một bản ghi của những gì đã được bao gồm / yêu cầu. Mỗi *_once
cuộc gọi có nghĩa là kiểm tra nhật ký đó. Vì vậy, chắc chắn có một số công việc bổ sung đang được thực hiện ở đó nhưng đủ để làm giảm tốc độ của toàn bộ ứng dụng?
... Tôi thực sự nghi ngờ điều đó ... Không trừ khi bạn sử dụng phần cứng thực sự cũ hoặc làm việc đó rất nhiều .
Nếu bạn đang làm hàng ngàn *_once
, bạn có thể tự mình thực hiện công việc theo cách nhẹ nhàng hơn. Đối với các ứng dụng đơn giản, chỉ cần đảm bảo rằng bạn đã chỉ bao gồm nó một lần nên là đủ nhưng nếu bạn vẫn nhận được lỗi Định nghĩa lại, bạn có thể một cái gì đó như thế này:
if (!defined('MyIncludeName')) {
require('MyIncludeName');
define('MyIncludeName', 1);
}
Cá nhân tôi sẽ gắn bó với các *_once
tuyên bố nhưng trên điểm chuẩn triệu ngớ ngẩn, bạn có thể thấy sự khác biệt giữa hai báo cáo:
php hhvm
if defined 0.18587779998779 0.046600103378296
require_once 1.2219581604004 3.2908599376678
10-100 × chậm hơn require_once
và nó tò mò require_once
dường như chậm hơn hhvm
. Một lần nữa, điều này chỉ liên quan đến mã của bạn nếu bạn đang chạy *_once
hàng ngàn lần.
<?php // test.php
$LIMIT = 1000000;
$start = microtime(true);
for ($i=0; $i<$LIMIT; $i++)
if (!defined('include.php')) {
require('include.php');
define('include.php', 1);
}
$mid = microtime(true);
for ($i=0; $i<$LIMIT; $i++)
require_once('include.php');
$end = microtime(true);
printf("if defined\t%s\nrequire_once\t%s\n", $mid-$start, $end-$mid);
<?php // include.php
// do nothing.
Chủ đề này làm cho tôi co rúm lại, bởi vì đã có một "giải pháp được đăng", và nó, cho tất cả các ý định và mục đích, sai. Hãy liệt kê:
Định nghĩa là thực sự tốn kém trong PHP. Bạn có thể tự tìm kiếm hoặc kiểm tra nó, nhưng cách hiệu quả duy nhất để xác định hằng số toàn cầu trong PHP là thông qua một phần mở rộng. (Các hằng số lớp thực sự là hiệu năng khá tốt khôn ngoan, nhưng đây là điểm moot, vì 2)
Nếu bạn đang sử dụng một cách require_once()
thích hợp, nghĩa là, để bao gồm các lớp, bạn thậm chí không cần một định nghĩa; Chỉ cần kiểm tra nếu class_exists('Classname')
. Nếu tệp bạn đang chứa có chứa mã, tức là bạn đang sử dụng nó theo kiểu thủ tục, hoàn toàn không có lý do nào require_once()
cần thiết cho bạn; mỗi lần bạn bao gồm tệp bạn cho là đang thực hiện cuộc gọi chương trình con.
Vì vậy, trong một thời gian, rất nhiều người đã sử dụng class_exists()
phương pháp này cho các vùi của họ. Tôi không thích nó bởi vì nó chạy trốn, nhưng họ có lý do chính đáng: require_once()
khá kém hiệu quả trước một số phiên bản gần đây của PHP. Nhưng điều đó đã được sửa, và tôi tranh cãi rằng mã byte phụ mà bạn phải biên dịch cho lệnh gọi có điều kiện và phương thức bổ sung, sẽ vượt xa bất kỳ kiểm tra hashtable nội bộ nào.
Bây giờ, một sự thừa nhận: công cụ này rất khó để kiểm tra, vì nó chiếm quá ít thời gian thực hiện.
Đây là câu hỏi bạn nên suy nghĩ: bao gồm, như một quy tắc chung, rất tốn kém trong PHP, bởi vì mỗi khi trình thông dịch chạm vào nó, nó phải chuyển trở lại chế độ phân tích cú pháp, tạo ra các opcode và sau đó nhảy trở lại. Nếu bạn có hơn 100 bao gồm, điều này chắc chắn sẽ có tác động hiệu suất. Lý do tại sao sử dụng hay không sử dụng allow_once là một câu hỏi quan trọng như vậy là vì nó gây khó khăn cho bộ nhớ opcode. Một lời giải thích cho điều này có thể được tìm thấy ở đây, nhưng điều này sôi nổi là:
Nếu trong thời gian phân tích cú pháp, bạn biết chính xác những gì bạn sẽ cần trong toàn bộ vòng đời của yêu cầu, require()
những tệp ngay từ đầu và bộ đệm opcode sẽ xử lý mọi thứ khác cho bạn.
Nếu bạn không chạy bộ đệm opcode, bạn đang ở một nơi khó khăn. Nội tuyến tất cả bao gồm của bạn vào một tệp (không làm điều này trong quá trình phát triển, chỉ trong sản xuất) chắc chắn có thể giúp phân tích thời gian, nhưng đó là một điều khó khăn, và, bạn cũng cần biết chính xác những gì bạn sẽ bao gồm trong quá trình yêu cầu.
Tự động tải rất thuận tiện, nhưng chậm, vì lý do logic tự động tải phải được chạy mỗi khi bao gồm xong. Trong thực tế, tôi đã thấy rằng tự động tải một số tệp chuyên dụng cho một yêu cầu không gây ra quá nhiều vấn đề, nhưng bạn không nên tự động tải tất cả các tệp bạn cần.
Nếu bạn có thể bao gồm 10 (đây là một rất sau của việc tính toán phong bì), tất cả wanking đây không phải là giá trị của nó: chỉ cần tối ưu hóa truy vấn cơ sở dữ liệu của bạn hoặc một cái gì đó.
define()
, require_once()
và defined()
tất cả chỉ mất khoảng 1-2 micro giây trên máy của tôi.
Tôi đã tò mò và kiểm tra liên kết của Adam Backstrom với Tech Your Universe . Bài viết này mô tả một trong những lý do cần phải được sử dụng thay vì allow_once. Tuy nhiên, tuyên bố của họ đã không theo kịp phân tích của tôi. Tôi muốn biết nơi tôi có thể đánh giá sai giải pháp. Tôi đã sử dụng PHP 5.2.0 để so sánh.
Tôi đã bắt đầu bằng cách tạo 100 tệp tiêu đề đã sử dụng allow_once để bao gồm một tệp tiêu đề khác. Mỗi tệp này trông giống như:
<?php
// /home/fbarnes/phpperf/hdr0.php
require_once "../phpperf/common_hdr.php";
?>
Tôi đã tạo những thứ này bằng cách sử dụng hack Bash nhanh chóng:
for i in /home/fbarnes/phpperf/hdr{00..99}.php; do
echo "<?php
// $i" > $i
cat helper.php >> $i;
done
Bằng cách này, tôi có thể dễ dàng trao đổi giữa việc sử dụng allow_once và yêu cầu khi bao gồm các tệp tiêu đề. Sau đó tôi đã tạo một app.php để tải một trăm tệp. Điều này trông giống như:
<?php
// Load all of the php hdrs that were created previously
for($i=0; $i < 100; $i++)
{
require_once "/home/fbarnes/phpperf/hdr$i.php";
}
// Read the /proc file system to get some simple stats
$pid = getmypid();
$fp = fopen("/proc/$pid/stat", "r");
$line = fread($fp, 2048);
$array = split(" ", $line);
// Write out the statistics; on RedHat 4.5 with kernel 2.6.9
// 14 is user jiffies; 15 is system jiffies
$cntr = 0;
foreach($array as $elem)
{
$cntr++;
echo "stat[$cntr]: $elem\n";
}
fclose($fp);
?>
Tôi đã đối chiếu các tiêu đề allow_once với các tiêu đề yêu cầu sử dụng tệp tiêu đề trông giống như:
<?php
// /home/fbarnes/phpperf/h/hdr0.php
if(!defined('CommonHdr'))
{
require "../phpperf/common_hdr.php";
define('CommonHdr', 1);
}
?>
Tôi không tìm thấy nhiều sự khác biệt khi chạy cái này với request so với allow_once. Trên thực tế, các thử nghiệm ban đầu của tôi dường như ngụ ý rằng request_once nhanh hơn một chút, nhưng tôi không nhất thiết phải tin vào điều đó. Tôi lặp lại thí nghiệm với 10000 tệp đầu vào. Ở đây tôi đã thấy một sự khác biệt nhất quán. Tôi đã chạy thử nghiệm nhiều lần, kết quả rất gần nhưng sử dụng allow_once sử dụng trung bình 30,8 jiffies người dùng và 72,6 jiffies hệ thống; sử dụng yêu cầu sử dụng trung bình 39,4 jiffies người dùng và 72.0 jiffies hệ thống. Do đó, có vẻ như tải thấp hơn một chút bằng cách sử dụng allow_once. Tuy nhiên, thời gian đồng hồ treo tường hơi tăng. Trung bình 10.000 cuộc gọi request_once sử dụng trung bình 10,15 giây để hoàn thành trung bình và 10.000 cuộc gọi yêu cầu sử dụng trung bình 9,84 giây.
Bước tiếp theo là xem xét những khác biệt này. Tôi đã sử dụng strace để phân tích các cuộc gọi hệ thống đang được thực hiện.
Trước khi mở tệp từ request_once, các cuộc gọi hệ thống sau đây được thực hiện:
time(NULL) = 1223772434
lstat64("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf/h", {st_mode=S_IFDIR|0755, st_size=270336, ...}) = 0
lstat64("/home/fbarnes/phpperf/h/hdr0.php", {st_mode=S_IFREG|0644, st_size=88, ...}) = 0
time(NULL) = 1223772434
open("/home/fbarnes/phpperf/h/hdr0.php", O_RDONLY) = 3
Điều này trái ngược với yêu cầu:
time(NULL) = 1223772905
lstat64("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf/h", {st_mode=S_IFDIR|0755, st_size=270336, ...}) = 0
lstat64("/home/fbarnes/phpperf/h/hdr0.php", {st_mode=S_IFREG|0644, st_size=146, ...}) = 0
time(NULL) = 1223772905
open("/home/fbarnes/phpperf/h/hdr0.php", O_RDONLY) = 3
Công nghệ Vũ trụ của bạn ngụ ý rằng request_once sẽ thực hiện nhiều cuộc gọi lstat64 hơn. Tuy nhiên, cả hai đều thực hiện cùng một số cuộc gọi lstat64. Có thể, sự khác biệt là tôi không chạy APC để tối ưu hóa mã ở trên. Tuy nhiên, tiếp theo tôi đã so sánh đầu ra của strace cho toàn bộ các lần chạy:
[fbarnes@myhost phpperf]$ wc -l strace_1000r.out strace_1000ro.out
190709 strace_1000r.out
210707 strace_1000ro.out
401416 total
Thực tế, có khoảng hai cuộc gọi hệ thống khác trên mỗi tệp tiêu đề khi sử dụng allow_once. Một điểm khác biệt là allow_once có thêm lệnh gọi hàm time ():
[fbarnes@myhost phpperf]$ grep -c time strace_1000r.out strace_1000ro.out
strace_1000r.out:20009
strace_1000ro.out:30008
Cuộc gọi hệ thống khác là getcwd ():
[fbarnes@myhost phpperf]$ grep -c getcwd strace_1000r.out strace_1000ro.out
strace_1000r.out:5
strace_1000ro.out:10004
Điều này được gọi bởi vì tôi đã quyết định đường dẫn tương đối được tham chiếu trong các tệp hdrXXX. Nếu tôi thực hiện điều này một tham chiếu tuyệt đối, thì sự khác biệt duy nhất là cuộc gọi thời gian bổ sung (NULL) được thực hiện trong mã:
[fbarnes@myhost phpperf]$ wc -l strace_1000r.out strace_1000ro.out
190705 strace_1000r.out
200705 strace_1000ro.out
391410 total
[fbarnes@myhost phpperf]$ grep -c time strace_1000r.out strace_1000ro.out
strace_1000r.out:20008
strace_1000ro.out:30008
Điều này dường như ngụ ý rằng bạn có thể giảm số lượng cuộc gọi hệ thống bằng cách sử dụng các đường dẫn tuyệt đối thay vì các đường dẫn tương đối. Sự khác biệt duy nhất bên ngoài đó là các cuộc gọi thời gian (NULL) dường như được sử dụng để ghi mã để so sánh cái nào nhanh hơn.
Một lưu ý khác là gói tối ưu hóa APC có một tùy chọn gọi là "apc.include_once_override" tuyên bố rằng nó giảm số lượng cuộc gọi hệ thống được thực hiện bởi các cuộc gọi allow_once và include_once (xem tài liệu PHP ).
Bạn có thể cung cấp cho chúng tôi bất kỳ liên kết đến các thực hành mã hóa mà nói để tránh nó? Theo tôi nghĩ, đó là một vấn đề hoàn toàn . Tôi đã không nhìn vào mã nguồn, nhưng tôi tưởng tượng rằng sự khác biệt duy nhất giữa include
và include_once
đó là include_once
thêm tên tệp đó vào một mảng và kiểm tra mảng mỗi lần. Thật dễ dàng để giữ cho mảng đó được sắp xếp, vì vậy tìm kiếm trên đó phải là O (log n) và ngay cả một ứng dụng cỡ trung bình cũng chỉ có vài chục bao gồm.
Cách tốt hơn để làm mọi việc là sử dụng cách tiếp cận hướng đối tượng và sử dụng __autoload () .
__autoload()
không được khuyến khích và nó có thể bị phản đối trong tương lai, bạn nên sử dụng spl_autoload_register(...)
những ngày này ... PS2: đôi khi tôi không sử dụng chức năng tự động tải; )
Nó không sử dụng chức năng xấu. Đó là một sự hiểu biết không chính xác về cách thức và thời điểm sử dụng nó, trong một cơ sở mã tổng thể. Tôi sẽ chỉ thêm một chút bối cảnh cho khái niệm có thể bị hiểu lầm:
Mọi người không nên nghĩ rằng request_once là một chức năng chậm. Bạn phải bao gồm mã của bạn bằng cách này hay cách khác. require_once()
so với require()
tốc độ không phải là vấn đề. Đó là về hiệu suất cản trở hiệu suất có thể dẫn đến việc sử dụng nó một cách mù quáng. Nếu được sử dụng rộng rãi mà không xem xét cho ngữ cảnh, nó có thể dẫn đến lãng phí bộ nhớ lớn hoặc mã lãng phí.
Những gì tôi đã thấy thực sự tồi tệ, là khi các khung nguyên khối khổng lồ sử dụng require_once()
sai tất cả các cách, đặc biệt là trong một môi trường hướng đối tượng (OO) phức tạp.
Lấy ví dụ về việc sử dụng require_once()
ở đầu mỗi lớp như đã thấy trong nhiều thư viện:
require_once("includes/usergroups.php");
require_once("includes/permissions.php");
require_once("includes/revisions.php");
class User{
// User functions
}
Vì vậy, User
lớp được thiết kế để sử dụng cả ba lớp khác. Đủ công bằng!
Nhưng bây giờ sẽ ra sao nếu khách truy cập đang duyệt trang web và thậm chí không đăng nhập và khung tải: require_once("includes/user.php");
cho mỗi yêu cầu.
Nó bao gồm 1 + 3 lớp không cần thiết mà nó sẽ không bao giờ sử dụng trong yêu cầu cụ thể đó. Đây là cách các khung cồng kềnh kết thúc bằng cách sử dụng 40 MB cho mỗi yêu cầu thay vì 5 MB hoặc ít hơn.
Các cách khác nó có thể bị sử dụng sai, là khi một lớp được sử dụng lại bởi nhiều người khác! Giả sử bạn có khoảng 50 lớp sử dụng helper
hàm. Để đảm bảo helpers
có sẵn cho các lớp đó khi chúng được tải, bạn nhận được:
require_once("includes/helpers.php");
class MyClass{
// Helper::functions(); // etc..
}
Không có gì sai ở đây mỗi se. Tuy nhiên nếu một yêu cầu trang xảy ra bao gồm 15 lớp tương tự. Bạn đang chạy require_once
15 lần hoặc cho một hình ảnh đẹp:
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
Việc sử dụng allow_once () về mặt kỹ thuật ảnh hưởng đến hiệu năng để chạy chức năng đó 14 lần, trên hết là phải phân tích các dòng không cần thiết đó. Chỉ với 10 lớp được sử dụng nhiều khác với vấn đề tương tự, nó có thể chiếm hơn 100 dòng mã lặp đi lặp lại khá vô nghĩa như vậy.
require("includes/helpers.php");
Thay vào đó, có lẽ đáng để sử dụng tại bootstrap của ứng dụng hoặc khung của bạn, thay vào đó. Nhưng vì mọi thứ đều tương đối, tất cả phụ thuộc vào việc trọng số so với tần suất sử dụng của helpers
lớp có đáng để tiết kiệm 15 - 100 dòng hay không require_once()
. Nhưng nếu xác suất không sử dụng helpers
tệp trên bất kỳ yêu cầu cụ thể nào là không có, thì require
chắc chắn nên ở trong lớp chính của bạn. Có require_once
trong mỗi lớp riêng biệt trở thành một sự lãng phí tài nguyên.
Các require_once
chức năng rất hữu ích khi cần thiết, nhưng nó không nên được coi là một giải pháp nguyên khối để sử dụng ở khắp mọi nơi để tải tất cả các lớp học.
Wiki PEAR2 (khi nó tồn tại) được sử dụng để liệt kê các lý do chính đáng để từ bỏ tất cả các chỉ thị yêu cầu / bao gồm có lợi cho việc tự động tải, ít nhất là cho mã thư viện. Những thứ này buộc bạn xuống các cấu trúc thư mục cứng nhắc khi các mô hình đóng gói thay thế như phar xuất hiện.
Cập nhật: Vì phiên bản lưu trữ web của wiki rất xấu, tôi đã sao chép những lý do hấp dẫn nhất dưới đây:
- include_path là bắt buộc để sử dụng gói (PEAR). Điều này gây khó khăn cho việc gói một gói PEAR trong một ứng dụng khác với bao gồm chính nó, để tạo một tệp duy nhất chứa các lớp cần thiết, để chuyển gói PEAR sang kho lưu trữ phar mà không cần sửa đổi mã nguồn mở rộng.
- khi request_once cấp cao nhất được trộn lẫn với request_once có điều kiện, điều này có thể dẫn đến mã không thể truy cập được bởi các bộ đệm opcode như APC, sẽ được gói cùng với PHP 6.
- Yêu cầu tương đối yêu cầu bao gồm bao gồm_path đã được thiết lập đúng giá trị, khiến cho không thể sử dụng gói mà không có bao gồm đúng_path
Các *_once()
chức năng stat mọi thư mục mẹ để đảm bảo tệp bạn bao gồm không giống với tệp đã được đưa vào. Đó là một phần lý do cho sự chậm lại.
Tôi khuyên bạn nên sử dụng một công cụ như Siege để đo điểm chuẩn. Bạn có thể thử tất cả các phương pháp được đề xuất và so sánh thời gian phản hồi.
Thêm vào require_once()
là tại Tech Your Universe .
Thậm chí nếu require_once
và include_once
là chậm hơn so với require
và include
(hoặc bất kỳ lựa chọn thay thế có thể tồn tại), chúng ta đang nói về mức độ nhỏ nhất của vi-tối ưu hóa ở đây. Thời gian của bạn tốt hơn dành cho việc tối ưu hóa vòng lặp hoặc truy vấn cơ sở dữ liệu được viết kém hơn là lo lắng về một cái gì đó như thế nào require_once
.
Bây giờ, người ta có thể đưa ra một lập luận nói rằng require_once
cho phép thực hành mã hóa kém bởi vì bạn không cần phải chú ý đến việc giữ cho bao gồm sạch sẽ và có tổ chức, nhưng điều đó không liên quan gì đến chính chức năng và đặc biệt là tốc độ của nó.
Rõ ràng, tự động tải tốt hơn vì mục đích sạch mã và dễ bảo trì, nhưng tôi muốn làm rõ rằng điều này không liên quan gì đến tốc độ .
Bạn kiểm tra, sử dụng bao gồm, thay thế của oli và __autoload (); và kiểm tra nó với một cái gì đó giống như APC được cài đặt.
Tôi nghi ngờ sử dụng liên tục sẽ tăng tốc mọi thứ.
Có, nó đắt hơn một chút so với yêu cầu (). Tôi nghĩ vấn đề là nếu bạn có thể giữ cho mã của mình được tổ chức đủ để không trùng lặp bao gồm, đừng sử dụng các hàm * _once (), vì nó sẽ giúp bạn tiết kiệm một số chu kỳ.
Nhưng sử dụng các hàm _once () sẽ không giết ứng dụng của bạn. Về cơ bản, chỉ cần không sử dụng nó như một cái cớ để không phải tổ chức bao gồm của bạn . Trong một số trường hợp, sử dụng nó vẫn không thể tránh khỏi, và nó không phải là vấn đề lớn.
Tôi nghĩ trong tài liệu PEAR, có một khuyến nghị cho các yêu cầu, allow_once, bao gồm và include_once. Tôi làm theo hướng dẫn đó. Ứng dụng của bạn sẽ rõ ràng hơn.
Nó không có gì để làm với tốc độ. Đó là về thất bại duyên dáng.
Nếu request_once () thất bại, tập lệnh của bạn đã hoàn thành. Không có gì khác được xử lý. Nếu bạn sử dụng include_once () phần còn lại của tập lệnh sẽ cố gắng tiếp tục hiển thị, do đó người dùng của bạn có khả năng sẽ không khôn ngoan hơn với điều gì đó đã thất bại trong tập lệnh của bạn.
Ý kiến cá nhân của tôi là việc sử dụng allow_once (hoặc include_once) là một cách làm không tốt bởi vì notify_once kiểm tra cho bạn nếu bạn đã bao gồm tệp đó và loại bỏ lỗi của các tệp được bao gồm gấp đôi dẫn đến lỗi nghiêm trọng (như khai báo trùng lặp các hàm / lớp / v.v.) .
Bạn nên biết nếu bạn cần bao gồm một tập tin.