Tôi hiện đang cố gắng cải thiện hiệu suất của một chương trình F # để làm cho nó nhanh như C# tương đương của nó. Chương trình này áp dụng một mảng bộ lọc cho một bộ đệm của các điểm ảnh. Truy cập vào bộ nhớ luôn được thực hiện bằng cách sử dụng con trỏ.Vấn đề hiệu suất thao tác hình ảnh F #
Đây là mã C# mà được áp dụng cho mỗi điểm ảnh của một hình ảnh:
unsafe private static byte getPixelValue(byte* buffer, double* filter, int filterLength, double filterSum)
{
double sum = 0.0;
for (int i = 0; i < filterLength; ++i)
{
sum += (*buffer) * (*filter);
++buffer;
++filter;
}
sum = sum/filterSum;
if (sum > 255) return 255;
if (sum < 0) return 0;
return (byte) sum;
}
Chiếc F # mã trông như thế này và phải mất ba lần miễn là chương trình C#:
let getPixelValue (buffer:nativeptr<byte>) (filterData:nativeptr<float>) filterLength filterSum : byte =
let rec accumulatePixel (acc:float) (buffer:nativeptr<byte>) (filter:nativeptr<float>) i =
if i > 0 then
let newAcc = acc + (float (NativePtr.read buffer) * (NativePtr.read filter))
accumulatePixel newAcc (NativePtr.add buffer 1) (NativePtr.add filter 1) (i-1)
else
acc
let acc = (accumulatePixel 0.0 buffer filterData filterLength)/filterSum
match acc with
| _ when acc > 255.0 -> 255uy
| _ when acc < 0.0 -> 0uy
| _ -> byte acc
Sử dụng các biến có thể thay đổi và một vòng lặp for trong F # không dẫn đến tốc độ giống như sử dụng đệ quy. Tất cả các dự án được cấu hình để chạy trong Chế độ phát hành với Tối ưu hóa mã được bật.
Làm cách nào để cải thiện hiệu suất của phiên bản F #?
EDIT:
Các nút cổ chai có vẻ là trong (NativePtr.get buffer offset)
. Nếu tôi thay thế mã này bằng một giá trị cố định và cũng thay thế mã tương ứng trong phiên bản C# bằng một giá trị cố định, tôi nhận được cùng tốc độ cho cả hai chương trình. Trong thực tế, trong C# tốc độ không thay đổi ở tất cả, nhưng trong F # nó làm cho một sự khác biệt rất lớn.
Hành vi này có thể bị thay đổi hoặc bắt nguồn từ sâu trong kiến trúc của F # không?
EDIT 2:
tôi refactored mã một lần nữa để sử dụng cho-vòng. Tốc độ thực thi vẫn giữ nguyên:
let mutable acc <- 0.0
let mutable f <- filterData
let mutable b <- tBuffer
for i in 1 .. filter.FilterLength do
acc <- acc + (float (NativePtr.read b)) * (NativePtr.read f)
f <- NativePtr.add f 1
b <- NativePtr.add b 1
Nếu tôi so sánh các mã IL của một phiên bản sử dụng (NativePtr.read b)
và một phiên bản đó là như nhau ngoại trừ việc nó sử dụng một giá trị cố định 111uy
thay vì đọc nó từ con trỏ, Chỉ những dòng sau trong thay đổi mã IL:
111uy
có IL-Code ldc.i4.s 0x6f
(0,3 giây)
(NativePtr.read b)
có dòng IL-Code ldloc.s b
và ldobj uint8
(1,4 giây)
Để so sánh: C# thực hiện việc lọc trong 0,4 giây.
Thực tế là việc đọc bộ lọc không ảnh hưởng đến hiệu suất trong khi đọc từ bộ đệm hình ảnh không bằng cách nào đó gây nhầm lẫn. Trước khi tôi lọc một dòng hình ảnh, tôi sao chép dòng vào một bộ đệm có độ dài của một dòng. Đó là lý do tại sao các hoạt động đọc không được trải rộng trên toàn bộ hình ảnh nhưng nằm trong bộ đệm này, có kích thước khoảng 800 byte.
Dựa trên nhận xét gần đây nhất của bạn, tôi tự hỏi, nếu thực tế là biên dịch C# sử dụng 'ldind.u1' thay vì 'ldobj uint8' (đó là IL tương đương ngữ nghĩa mà F # sử dụng) tạo nên sự khác biệt. Bạn có thể thử chạy ildasm trên thực thi F #, thay thế 'ldobj uint8' bằng' ldind.u1' và chạy ilasm trên nó để xem hiệu suất có khác không. – kvb
Tôi đã thay thế nó nhưng không có sự khác biệt. Dù sao cũng cảm ơn bạn. –