Lưu ý - hiện tại có giải pháp "sạch" ở đây, vì vậy hãy chuyển sang bản chỉnh sửa cuối cùng nếu bạn chỉ muốn phiên bản chạy nhanh và không quan tâm đến tất cả công việc thám tử.
Dường như không có sự khác biệt giữa các cuộc gọi trực tiếp và cuộc gọi ảo gây ra sự chậm lại. Đó là một cái gì đó để làm với những đại biểu; Tôi không thể giải thích cụ thể nó là gì, nhưng nhìn vào IL tạo ra được hiển thị rất nhiều đại biểu lưu trữ mà tôi nghĩ có thể không được sử dụng trong phiên bản lớp cơ sở. Nhưng bản thân IL dường như không khác biệt đáng kể giữa hai phiên bản, điều này khiến tôi tin rằng bản thân jitter có trách nhiệm một phần.
Hãy xem refactoring này, cắt thời gian chạy khoảng 60%:
public virtual TreeType Insert(T value)
{
Func<TreeType, T, TreeType, TreeType> nodeFunc = (l, x, r) =>
{
int compare = this.Comparer(value, x);
if (compare < 0) { return CreateNode(l.Insert(value), x, r); }
else if (compare > 0) { return CreateNode(l, x, r.Insert(value)); }
return Self();
};
return Insert<TreeType>(value, nodeFunc);
}
private TreeType Insert<U>(T value,
Func<TreeType, T, TreeType, TreeType> nodeFunc)
{
return this.Match<TreeType>(
() => CreateNode(Self(), value, Self()),
nodeFunc);
}
này nên (và dường như không) đảm bảo rằng các đại biểu chèn chỉ được tạo ra một lần mỗi chèn - nó không được tạo trên mỗi lần đệ quy. Trên máy tính của tôi, nó giảm thời gian chạy từ 350 ms xuống còn 120 ms (ngược lại, phiên bản một lớp chạy trong khoảng 30 ms, vì vậy đây vẫn là hư không gần nơi nó nên ở).
Nhưng đây là nơi nó thậm chí còn khó hơn - sau khi thử việc tái cấu trúc ở trên, tôi nghĩ, hmm, có lẽ nó vẫn còn chậm vì tôi chỉ làm một nửa công việc. Vì vậy, tôi đã cố gắng thể hóa các đại biểu đầu tiên cũng như:
public virtual TreeType Insert(T value)
{
Func<TreeType> nilFunc =() => CreateNode(Self(), value, Self());
Func<TreeType, T, TreeType, TreeType> nodeFunc = (l, x, r) =>
{
int compare = this.Comparer(value, x);
if (compare < 0) { return CreateNode(l.Insert(value), x, r); }
else if (compare > 0) { return CreateNode(l, x, r.Insert(value)); }
return Self();
};
return Insert<TreeType>(value, nilFunc, nodeFunc);
}
private TreeType Insert<U>(T value, Func<TreeType> nilFunc,
Func<TreeType, T, TreeType, TreeType> nodeFunc)
{
return this.Match<TreeType>(nilFunc, nodeFunc);
}
Và đoán những gì ... Đây đã làm cho nó chậm một lần nữa! Với phiên bản này, trên máy tính của tôi, nó mất hơn 250 ms trong lần chạy này.
Điều này bất chấp tất cả các giải thích hợp lý có thể liên quan đến vấn đề với bytecode được biên dịch, đó là lý do tại sao tôi nghi ngờ rằng jitter nằm trong âm mưu này. Tôi nghĩ rằng "tối ưu hóa" đầu tiên ở trên có thể là (CẢNH BÁO - đầu cơ trước) cho phép chèn đại biểu được inlined - đó là một thực tế được biết rằng jitter không thể gọi nội tuyến ảo - nhưng vẫn còn cái gì khác đó là không nội tuyến và đó là nơi tôi hiện đang bối rối.
Bước tiếp theo của tôi là vô hiệu hóa chọn lọc nội tuyến trên các phương thức nhất định qua MethodImplAttribute
và xem hiệu ứng nào có trong thời gian chạy - điều đó sẽ giúp chứng minh hoặc bác bỏ lý thuyết này.
Tôi biết đây không phải là câu trả lời hoàn chỉnh nhưng hy vọng ít nhất nó sẽ cung cấp cho bạn một số thứ để làm việc và có thể một số thử nghiệm tiếp theo với sự phân hủy này có thể tạo ra kết quả gần với hiệu suất cho phiên bản gốc.
Chỉnh sửa: Hah, ngay sau khi tôi trình này, tôi loạng choạng về tối ưu hóa khác. Nếu bạn thêm phương pháp này để các lớp cơ sở:
private TreeType CreateNilNode(T value)
{
return CreateNode(Self(), value, Self());
}
Bây giờ thời gian chạy giảm xuống còn 38 ms ở đây, chỉ vừa đủ so với phiên bản gốc. Điều này thổi tâm trí của tôi, bởi vì không có gì thực sự tham khảo phương pháp này! Phương thức riêng tư Insert<U>
vẫn giống với khối mã đầu tiên trong câu trả lời của tôi. Tôi đã chuyển sang số để thay đổi đối số đầu tiên tham chiếu phương thức CreateNilNode
, nhưng tôi không phải làm như vậy. Hoặc là jitter đang thấy rằng các đại biểu vô danh là giống như phương pháp CreateNilNode
và chia sẻ cơ thể (có thể nội tuyến một lần nữa), hoặc ... hoặc, tôi không biết. Đây là ví dụ đầu tiên tôi từng chứng kiến khi thêm một phương thức riêng và không bao giờ gọi nó là có thể tăng tốc một chương trình với hệ số là 4.
Bạn sẽ phải kiểm tra điều này để đảm bảo rằng tôi đã không vô tình giới thiệu bất kỳ lỗi logic nào - chắc chắn là không, mã gần như giống nhau - nhưng nếu tất cả đều kiểm tra, thì bạn ở đây, điều này chạy nhanh như không bắt nguồn từ AvlTree
.
THÊM CẬP NHẬT
tôi đã có thể đưa ra một phiên bản của sự kết hợp cơ sở/có nguồn gốc thực sự chạy hơi nhanh so với phiên bản duy nhất lớp. Có một số coaxing, nhưng nó hoạt động!
Những gì chúng tôi cần làm là tạo một trình bổ sung dành riêng có thể tạo tất cả các đại biểu chỉ một lần mà không cần phải làm bất kỳ việc thu giữ biến nào. Thay vào đó, tất cả trạng thái được lưu trữ trong các trường thành viên. Đặt này bên trong lớp BaseBinaryTree
:
protected class Inserter
{
private TreeType tree;
private Func<TreeType> nilFunc;
private Func<TreeType, T, TreeType, TreeType> nodeFunc;
private T value;
public Inserter(T value)
{
this.nilFunc =() => CreateNode();
this.nodeFunc = (l, x, r) => PerformMatch(l, x, r);
this.value = value;
}
public TreeType Insert(TreeType parent)
{
this.tree = parent;
return tree.Match<TreeType>(nilFunc, nodeFunc);
}
private TreeType CreateNode()
{
return tree.CreateNode(tree, value, tree);
}
private TreeType PerformMatch(TreeType l, T x, TreeType r)
{
int compare = tree.Comparer(value, x);
if (compare < 0) { return tree.CreateNode(l.Insert(value, this), x, r); }
else if (compare > 0) { return tree.CreateNode(l, x, r.Insert(value, this)); }
return tree;
}
}
Vâng, vâng, tôi biết, nó rất un-chức năng sử dụng mà có thể thay đổi trạng thái nội tree
, nhưng hãy nhớ rằng đây không phải là cây bản thân, nó chỉ là một không dùng nữa "Runnable "ví dụ. Không ai từng nói rằng perf-opt là khá! Đây là cách duy nhất để tránh tạo một Inserter
mới cho mỗi cuộc gọi đệ quy, nếu không sẽ làm chậm điều này xuống trên tài khoản của tất cả các phân bổ mới của Inserter
và các đại biểu nội bộ của nó.
Bây giờ thay thế các phương pháp chèn của lớp cơ sở như sau:
public TreeType Insert(T value)
{
return Insert(value, null);
}
protected virtual TreeType Insert(T value, Inserter inserter)
{
if (inserter == null)
{
inserter = new Inserter(value);
}
return inserter.Insert(Self());
}
tôi đã thực hiện các công Insert
phương pháp phi ảo; tất cả các công việc thực tế được giao cho một phương pháp được bảo vệ (hoặc tạo riêng của nó) Inserter
ví dụ. Thay đổi lớp được thừa kế rất đơn giản đủ, chỉ cần thay thế các phương pháp ghi đè Insert
với điều này:
protected override DerivedAvlTree<T> Insert(T value, Inserter inserter)
{
return base.Insert(value, inserter).Balance();
}
Vậy là xong. Bây giờ chạy cái này. Nó sẽ mất gần như cùng một lượng thời gian giống như AvlTree
, thường ít hơn vài phần nghìn giây trong bản phát hành bản phát hành.
Sự chậm lại rõ ràng là do một số kết hợp cụ thể của các phương pháp ảo, các phương thức ẩn danh và ghi biến số bằng cách nào đó ngăn cản jitter thực hiện tối ưu hóa quan trọng. Tôi không chắc chắn rằng nó là nội tuyến nữa, nó có thể chỉ là bộ nhớ đệm các đại biểu, nhưng tôi nghĩ rằng những người duy nhất có thể thực sự xây dựng được những người jitter jitter mình.
Tôi thực sự đã viết các triển khai của riêng mình về cây AVL và cây Đỏ đen và sử dụng phương pháp tương tự như bạn đang phàn nàn (có chuỗi thừa kế lên lớp cơ sở trừu tượng). Tôi đã không bao giờ hài lòng với hiệu suất, nhưng đặt mã sang một bên trước đây. Bạn đã tôn trọng sự tò mò của tôi trong điều này. – Nick