2009-12-21 45 views
18

Tôi đã làm một số thử nghiệm hiệu suất, chủ yếu là vì vậy tôi có thể hiểu sự khác biệt giữa các trình vòng lặp và đơn giản cho các vòng lặp. Là một phần của điều này, tôi đã tạo ra một bộ kiểm tra đơn giản và sau đó hoàn toàn ngạc nhiên bởi kết quả. Đối với một số phương pháp, 64 bit nhanh hơn gần 10 lần so với 32 bit.Tại sao điều này nhanh hơn trên 64 bit so với 32 bit?

Điều tôi đang tìm kiếm là một số giải thích cho lý do tại sao điều này xảy ra.

[Câu trả lời dưới đây nêu rõ điều này là do số học 64 bit trong ứng dụng 32 bit. Thay đổi độ dài để ints cho kết quả hoạt động tốt trên các hệ thống 32 bit và 64 bit.]

Dưới đây là 3 phương pháp được đề cập.

private static long ForSumArray(long[] array) 
{ 
    var result = 0L; 
    for (var i = 0L; i < array.LongLength; i++) 
    { 
     result += array[i]; 
    } 
    return result; 
} 

private static long ForSumArray2(long[] array) 
{ 
    var length = array.LongLength; 
    var result = 0L; 
    for (var i = 0L; i < length; i++) 
    { 
     result += array[i]; 
    } 
    return result; 
} 

private static long IterSumArray(long[] array) 
{ 
    var result = 0L; 
    foreach (var entry in array) 
    { 
     result += entry; 
    } 
    return result; 
} 

Tôi có một khai thác thử nghiệm đơn giản mà các xét nghiệm này

var repeat = 10000; 

var arrayLength = 100000; 
var array = new long[arrayLength]; 
for (var i = 0; i < arrayLength; i++) 
{ 
    array[i] = i; 
} 

Console.WriteLine("For: {0}", AverageRunTime(repeat,() => ForSumArray(array))); 

repeat = 100000; 
Console.WriteLine("For2: {0}", AverageRunTime(repeat,() => ForSumArray2(array))); 
Console.WriteLine("Iter: {0}", AverageRunTime(repeat,() => IterSumArray(array))); 

private static TimeSpan AverageRunTime(int count, Action method) 
{ 
    var stopwatch = new Stopwatch(); 
    stopwatch.Start(); 
    for (var i = 0; i < count; i++) 
    { 
     method(); 
    } 
    stopwatch.Stop(); 
    var average = stopwatch.Elapsed.Ticks/count; 
    return new TimeSpan(average); 
} 

Khi tôi chạy này, tôi nhận được kết quả như sau:
32 bit:

For: 00:00:00.0006080 
For2: 00:00:00.0005694 
Iter: 00:00:00.0001717

64 chút

For: 00:00:00.0007421 
For2: 00:00:00.0000814 
Iter: 00:00:00.0000818

Những điều tôi đọc từ đây là sử dụng LongLength chậm. Nếu tôi sử dụng array.Length, hiệu suất cho vòng lặp đầu tiên là khá tốt trong 64 bit, nhưng không phải 32 bit.

Điều khác tôi đọc từ việc này là lặp qua một mảng có hiệu quả như vòng lặp for, và mã này sạch hơn và dễ đọc hơn!

+0

Điều tôi cũng thấy thú vị là rõ ràng trình biên dịch JIT không tối ưu hóa việc truy cập mảng.LongLength. – newgre

Trả lời

50

x64 chứa các thanh ghi đa năng 64 bit có thể tính toán các phép toán trên các số nguyên 64 bit trong một lệnh duy nhất. Bộ vi xử lý 32 bit không có. Điều này đặc biệt liên quan đến chương trình của bạn vì nó sử dụng nhiều biến số long (số nguyên 64 bit).

Ví dụ, trong lắp ráp x64, để thêm một số nguyên 64 bit vài lưu trữ trong thanh ghi, bạn chỉ có thể làm:

; adds rbx to rax 
add rax, rbx 

Để làm các hoạt động tương tự trên một bộ xử lý x86 32 bit, bạn sẽ phải sử dụng hai thanh ghi và tự sử dụng các carry của hoạt động đầu tiên trong hoạt động thứ hai:

; adds ecx:ebx to edx:eax 
add eax, ebx 
adc edx, ecx 

More hướng dẫn và đăng ký ít hơn có nghĩa là chu kỳ đồng hồ nhiều hơn, nhớ lấy về, ... mà cuối cùng sẽ dẫn đến hiệu suất giảm. Sự khác biệt là rất đáng chú ý trong các ứng dụng crunching số.

Đối với các ứng dụng .NET, có vẻ như trình biên dịch JIT 64 bit thực hiện các tối ưu hóa tích cực hơn để cải thiện hiệu suất tổng thể.

Về điểm của bạn về lặp lại mảng, trình biên dịch C# đủ thông minh để nhận ra foreach qua mảng và xử lý chúng một cách đặc biệt. Mã được tạo giống hệt với việc sử dụng vòng lặp for và bạn nên sử dụng foreach nếu bạn không cần thay đổi phần tử mảng trong vòng lặp.Bên cạnh đó, thời gian chạy nhận ra các mẫu for (int i = 0; i < a.Length; ++i) và bỏ qua các kiểm tra ràng buộc cho các truy cập mảng bên trong vòng lặp. Điều này sẽ không xảy ra trong trường hợp LongLength và sẽ dẫn đến giảm hiệu suất (cả trường hợp 32 bit và 64 bit); và vì bạn sẽ sử dụng các biến số long với LongLength, hiệu suất 32 bit sẽ bị xuống cấp nhiều hơn nữa.

+4

Số lượng thanh ghi cũng được tăng lên trong bộ vi xử lý x64, nhưng chúng không sử dụng những thanh ghi này khi chạy mã 32 bit, chỉ có mã 64 bit. – Powerlord

+0

Nhận xét tuyệt vời về trình biên dịch C# và foreach, đặc biệt là kiểm tra giới hạn cho truy cập mảng! –

1

Không chắc chắn về "lý do" nhưng tôi chắc chắn sẽ gọi "phương thức" của bạn ít nhất một lần bên ngoài vòng hẹn giờ để bạn không tính lần đầu tiên. (Vì nó trông giống C# với tôi). Các bộ xử lý

5

Kiểu dữ liệu dài là 64 bit và trong quy trình 64 bit, nó được xử lý dưới dạng đơn vị độ dài gốc duy nhất. Trong quy trình 32 bit, nó được coi là 2 đơn vị 32 bit. Toán học, đặc biệt là trên các loại "phân chia" này sẽ được xử lý chuyên sâu.

1

Ồ, thật dễ dàng. Tôi cho rằng bạn đang sử dụng công nghệ x86. Bạn cần gì để thực hiện các vòng lặp trong assembler?

  1. Một biến chỉ số i
  2. Một kết quả kết quả biến
  3. Một mảng dài của kết quả.

Vì vậy, bạn cần ba biến. Truy cập biến là nhanh nhất nếu bạn có thể lưu trữ chúng trong sổ đăng ký; nếu bạn cần phải di chuyển chúng vào và ra để nhớ, bạn đang mất tốc độ. Đối với số 64bit bạn cần hai thanh ghi trên 32 bit và chúng tôi chỉ có bốn thanh ghi, vì vậy rất có thể tất cả các biến không thể được lưu trong sổ đăng ký, nhưng phải được lưu trữ trong bộ nhớ trung gian như ngăn xếp. Điều này một mình sẽ làm chậm truy cập đáng kể.

Bổ sung số: Bổ sung phải là hai lần; lần đầu tiên không mang bit và lần thứ hai với bit mang theo. 64bit nó có thể làm trong một chu kỳ.

Di chuyển/Tải: Đối với mỗi chu kỳ 1-bit 64 bit, bạn cần hai chu kỳ cho 32 bit để tải/dỡ một số nguyên dài vào bộ nhớ.

Mỗi kiểu dữ liệu thành phần (kiểu dữ liệu bao gồm nhiều bit hơn bit đăng ký/địa chỉ) sẽ mất tốc độ đáng kể. Tốc độ tăng của một bậc độ lớn là lý do GPU vẫn thích phao (32bit) thay vì tăng gấp đôi (64bit).

0

Như những người khác đã nói, làm số học 64 bit trên máy 32 bit sẽ mất thêm một số thao tác, hơn thế, nếu làm phép nhân hoặc chia. Quay lại mối quan tâm của bạn về vòng lặp so với đơn giản cho vòng lặp, trình lặp có thể có định nghĩa khá phức tạp, và chúng sẽ chỉ nhanh nếu nội tuyến và tối ưu hóa trình biên dịch có khả năng thay thế chúng bằng biểu mẫu đơn giản tương đương. Nó thực sự phụ thuộc vào loại trình lặp và việc triển khai container bên dưới. Cách đơn giản nhất để biết liệu nó có được tối ưu hóa hợp lý hay không là kiểm tra mã assembly đã tạo. Một cách khác là đặt nó trong một vòng lặp chạy dài, tạm dừng nó và nhìn vào ngăn xếp để xem nó đang làm gì.

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