Tôi hiện đang tối ưu hóa thư viện cấp thấp và đã tìm thấy trường hợp phản trực giác. Cam kết gây ra câu hỏi này là here.C# Tại sao sử dụng phương pháp thể hiện làm đại biểu phân bổ các đối tượng tạm thời GC0 nhưng nhanh hơn 10% so với đại biểu được lưu trong bộ nhớ cache
Có một đại biểu
public delegate void FragmentHandler(UnsafeBuffer buffer, int offset, int length, Header header);
và một phương pháp dụ
public void OnFragment(IDirectBuffer buffer, int offset, int length, Header header)
{
_totalBytes.Set(_totalBytes.Get() + length);
}
On this line, nếu tôi sử dụng phương pháp này như một đại biểu, chương trình phân bổ nhiều GC0 cho các đại biểu wrapper temp, nhưng hiệu suất nhanh hơn 10% (nhưng không ổn định).
var fragmentsRead = image.Poll(OnFragment, MessageCountLimit);
Nếu tôi thay vì bộ nhớ cache phương pháp trong một đại biểu bên ngoài vòng lặp như thế này:
FragmentHandler onFragmentHandler = OnFragment;
sau đó chương trình không phân bổ ở tất cả, con số này là rất ổn định nhưng chậm hơn nhiều.
Tôi đã xem qua IL được tạo và nó đang thực hiện tương tự, nhưng trong trường hợp sau newobj
được gọi chỉ một lần và sau đó biến cục bộ nếu được tải.
Với cache IL_0034 đại biểu:
IL_002d: ldarg.0
IL_002e: ldftn instance void Adaptive.Aeron.Samples.IpcThroughput.IpcThroughput/Subscriber::OnFragment(class [Adaptive.Agrona]Adaptive.Agrona.IDirectBuffer, int32, int32, class [Adaptive.Aeron]Adaptive.Aeron.LogBuffer.Header)
IL_0034: newobj instance void [Adaptive.Aeron]Adaptive.Aeron.LogBuffer.FragmentHandler::.ctor(object, native int)
IL_0039: stloc.3
IL_003a: br.s IL_005a
// loop start (head: IL_005a)
IL_003c: ldloc.0
IL_003d: ldloc.3
IL_003e: ldsfld int32 Adaptive.Aeron.Samples.IpcThroughput.IpcThroughput::MessageCountLimit
IL_0043: callvirt instance int32 [Adaptive.Aeron]Adaptive.Aeron.Image::Poll(class [Adaptive.Aeron]Adaptive.Aeron.LogBuffer.FragmentHandler, int32)
IL_0048: stloc.s fragmentsRead
Với phân bổ tạm IL_0037:
IL_002c: stloc.2
IL_002d: br.s IL_0058
// loop start (head: IL_0058)
IL_002f: ldloc.0
IL_0030: ldarg.0
IL_0031: ldftn instance void Adaptive.Aeron.Samples.IpcThroughput.IpcThroughput/Subscriber::OnFragment(class [Adaptive.Agrona]Adaptive.Agrona.IDirectBuffer, int32, int32, class [Adaptive.Aeron]Adaptive.Aeron.LogBuffer.Header)
IL_0037: newobj instance void [Adaptive.Aeron]Adaptive.Aeron.LogBuffer.FragmentHandler::.ctor(object, native int)
IL_003c: ldsfld int32 Adaptive.Aeron.Samples.IpcThroughput.IpcThroughput::MessageCountLimit
IL_0041: callvirt instance int32 [Adaptive.Aeron]Adaptive.Aeron.Image::Poll(class [Adaptive.Aeron]Adaptive.Aeron.LogBuffer.FragmentHandler, int32)
IL_0046: stloc.s fragmentsRead
Tại sao mã với phân bổ là nhanh hơn ở đây? Điều gì là cần thiết để tránh phân bổ nhưng giữ hiệu suất?
(thử nghiệm trên NET 4.5.2/4.6.1, x64, phát hành, trên hai máy khác nhau)
Cập nhật
Dưới đây là ví dụ độc lập mà ứng xử như mong đợi: đại biểu lưu trữ thực hiện hơn nhanh hơn 2 lần với 4 giây và 11 giây. Vì vậy, câu hỏi là cụ thể cho các dự án tham chiếu - những vấn đề tinh tế với trình biên dịch JIT hoặc cái gì khác có thể gây ra kết quả bất ngờ?
using System;
using System.Diagnostics;
namespace TestCachedDelegate {
public delegate int TestDelegate(int first, int second);
public static class Program {
static void Main(string[] args)
{
var tc = new TestClass();
tc.Run();
}
public class TestClass {
public void Run() {
var sw = new Stopwatch();
sw.Restart();
for (int i = 0; i < 1000000000; i++) {
CallDelegate(Add, i, i);
}
sw.Stop();
Console.WriteLine("Non-cached: " + sw.ElapsedMilliseconds);
sw.Restart();
TestDelegate dlgCached = Add;
for (int i = 0; i < 1000000000; i++) {
CallDelegate(dlgCached, i, i);
}
sw.Stop();
Console.WriteLine("Cached: " + sw.ElapsedMilliseconds);
Console.ReadLine();
}
public int CallDelegate(TestDelegate dlg, int first, int second) {
return dlg(first, second);
}
public int Add(int first, int second) {
return first + second;
}
}
}
}
tôi đề nghị bạn viết [MCVE] của vấn đề của bạn mà không liên quan đến mã cụ thể tên miền của bạn, vì vậy điều này có thể được sao chép trên các máy khác. –
Cam kết của bạn hơi không rõ ràng, giới thiệu một biến có cùng tên với trường lớp ngoại trừ dấu gạch dưới hàng đầu và phiên bản cũ đề cập đến trường lớp. Bạn chắc chắn bạn đã thử nghiệm với biến địa phương, tôi hy vọng? – hvd
@hvd Biến cục bộ bên trong vòng lặp hoạt động giống như sử dụng phương thức bên trong hàm '.Poll()'. Biến cục bộ bên ngoài vòng lặp hoạt động tương tự như một trường. –