Đầu tiên, hầu hết các JVM đều bao gồm một trình biên dịch, do đó "mã byte được giải thích" thực sự khá hiếm (ít nhất là trong mã điểm chuẩn - nó không hoàn toàn hiếm trong cuộc sống thực, nơi mã của bạn thường nhiều hơn một vài vòng lặp thường được lặp lại thường xuyên ).
Thứ hai, một số lượng lớn các điểm chuẩn liên quan dường như khá sai lệch (cho dù do cố ý hay không đủ năng lực, tôi thực sự không thể nói). Ví dụ, nhiều năm trước tôi đã xem xét một số mã nguồn được liên kết từ một trong những liên kết bạn đã đăng. Nó có mã như thế này:
init0 = (int*)calloc(max_x,sizeof(int));
init1 = (int*)calloc(max_x,sizeof(int));
init2 = (int*)calloc(max_x,sizeof(int));
for (x=0; x<max_x; x++) {
init2[x] = 0;
init1[x] = 0;
init0[x] = 0;
}
Vì việc calloc
cung cấp bộ nhớ đã bị xóa không, sử dụng for
vòng lặp về 0 nên rõ ràng là vô dụng. Điều này đã được theo dõi (nếu bộ nhớ phục vụ) bằng cách lấp đầy bộ nhớ với dữ liệu khác (và không phụ thuộc vào nó bằng 0), do đó, tất cả các zeroing là hoàn toàn không cần thiết. Việc thay thế mã ở trên bằng một cách đơn giản malloc
(giống như bất kỳ người lành mạnh nào đã từng sử dụng để bắt đầu) đã cải thiện tốc độ của phiên bản C ++ đủ để đánh bại phiên bản Java (bằng một lề khá rộng, nếu bộ nhớ phục vụ).
Xem xét (cho một ví dụ khác) methcall
điểm chuẩn được sử dụng trong mục blog trong liên kết cuối cùng của bạn. Mặc dù tên (và mọi thứ thậm chí có thể trông như thế nào), phiên bản C ++ này không thực sự đo lường nhiều về chi phí cuộc gọi phương thức. Phần mã hóa ra rất quan trọng là trong lớp Toggle:
class Toggle {
public:
Toggle(bool start_state) : state(start_state) { }
virtual ~Toggle() { }
bool value() {
return(state);
}
virtual Toggle& activate() {
state = !state;
return(*this);
}
bool state;
};
Phần quan trọng hóa ra là state = !state;
. Hãy xem xét những gì xảy ra khi chúng ta thay đổi mã để mã hóa trạng thái int
thay vì bool
:
class Toggle {
enum names{ bfalse = -1, btrue = 1};
const static names values[2];
int state;
public:
Toggle(bool start_state) : state(values[start_state])
{ }
virtual ~Toggle() { }
bool value() { return state==btrue; }
virtual Toggle& activate() {
state = -state;
return(*this);
}
};
Thay đổi nhỏ này cải thiện tốc độ tổng thể khoảng 5: 1 . Mặc dù điểm chuẩn được dự định để đo thời gian gọi phương thức, nhưng thực tế hầu hết những gì nó đo được là thời gian để chuyển đổi giữa int
và bool
. Tôi chắc chắn đồng ý rằng sự kém hiệu quả được hiển thị bởi bản gốc là không may - nhưng do hiếm khi nó xuất hiện trong mã thực và sự dễ dàng có thể được khắc phục khi / nếu nó phát sinh, tôi có một thời gian khó khăn để suy nghĩ Nó có ý nghĩa nhiều.
Trong trường hợp bất kỳ ai quyết định chạy lại các điểm chuẩn liên quan, tôi cũng nên nói thêm rằng có một sửa đổi gần như tầm thường đối với phiên bản Java tạo ra (hoặc ít nhất một lần được tạo ra - Tôi đã không chạy lại các thử nghiệm với JVM gần đây để xác nhận họ vẫn làm) một cải tiến khá đáng kể trong phiên bản Java. Phiên bản Java có NthToggle :: activ () trông như thế này:
public Toggle activate() {
this.counter += 1;
if (this.counter >= this.count_max) {
this.state = !this.state;
this.counter = 0;
}
return(this);
}
Thay đổi điều này để gọi hàm cơ bản thay vì thao tác this.state
trực tiếp giúp cải thiện tốc độ đáng kể (mặc dù không đủ để theo kịp phiên bản C ++ đã sửa đổi).
Vì vậy, những gì chúng tôi kết thúc là một giả định sai về mã byte được giải thích so với một số điểm chuẩn tồi tệ nhất (tôi) từng thấy. Không phải là đưa ra một kết quả có ý nghĩa.
Kinh nghiệm của riêng tôi là với các lập trình viên có kinh nghiệm như nhau, chú ý đến việc tối ưu hóa như nhau, C ++ sẽ đánh bại Java thường xuyên hơn không - nhưng (ít nhất là giữa hai điều này), ngôn ngữ sẽ hiếm khi tạo ra sự khác biệt nhiều như các lập trình viên và thiết kế. Các điểm chuẩn được trích dẫn cho chúng tôi biết nhiều hơn về năng lực (trong) / (không) trung thực của các tác giả của họ so với các ngôn ngữ mà họ hướng đến điểm chuẩn.
[Chỉnh sửa: Như được ngụ ý ở một nơi ở trên nhưng không bao giờ được tuyên bố trực tiếp như tôi có thể có, kết quả tôi trích dẫn là những gì tôi đã nhận được khi thử nghiệm điều này ~ 5 năm trước, sử dụng các triển khai C ++ và Java hiện tại vào thời điểm đó . Tôi đã không chạy lại các bài kiểm tra với các triển khai hiện tại. Tuy nhiên, nhìn thoáng qua cho thấy mã chưa được sửa, vì vậy tất cả những gì đã thay đổi sẽ là khả năng của trình biên dịch để che giấu các vấn đề trong mã.]
Nếu chúng ta bỏ qua những ví dụ Java, tuy nhiên, nó là thực sự có thể giải thích cho mã để chạy nhanh hơn so với mã biên dịch (mặc dù khó khăn và hơi bất thường).
Cách thông thường này xảy ra là mã được diễn giải nhỏ gọn hơn nhiều so với mã máy hoặc nó đang chạy trên CPU có bộ đệm dữ liệu lớn hơn bộ đệm mã.
Trong trường hợp như vậy, một trình thông dịch nhỏ (ví dụ: trình thông dịch bên trong của triển khai Forth) có thể hoàn toàn phù hợp với bộ đệm mã và chương trình phiên dịch phù hợp hoàn toàn với bộ đệm dữ liệu. Bộ nhớ cache thường nhanh hơn bộ nhớ chính theo hệ số ít nhất là 10 và thường là nhiều hơn (hệ số 100 không đặc biệt hiếm gặp nữa).
Vì vậy, nếu bộ đệm nhanh hơn bộ nhớ chính theo hệ số N và cần ít hơn hướng dẫn mã máy N để thực hiện từng mã byte, mã byte sẽ giành chiến thắng (Tôi đơn giản hóa, nhưng tôi nghĩ rằng ý tưởng chung vẫn nên được rõ ràng).