2010-01-23 25 views
20

Việc lập hồ sơ ứng dụng C# của tôi cho biết rằng thời gian đáng kể được chi tiêu trong List<T>.AddRange. Sử dụng Reflector để nhìn vào đoạn code trong phương pháp này chỉ ra rằng nó gọi List<T>.InsertRange đó được thực hiện như vậy:Danh sách <T> .Thêm vào thực hiện tối ưu hóa

public void InsertRange(int index, IEnumerable<T> collection) 
{ 
    if (collection == null) 
    { 
     ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection); 
    } 
    if (index > this._size) 
    { 
     ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_Index); 
    } 
    ICollection<T> is2 = collection as ICollection<T>; 
    if (is2 != null) 
    { 
     int count = is2.Count; 
     if (count > 0) 
     { 
      this.EnsureCapacity(this._size + count); 
      if (index < this._size) 
      { 
       Array.Copy(this._items, index, this._items, index + count, this._size - index); 
      } 
      if (this == is2) 
      { 
       Array.Copy(this._items, 0, this._items, index, index); 
       Array.Copy(this._items, (int) (index + count), this._items, (int) (index * 2), (int) (this._size - index)); 
      } 
      else 
      { 
       T[] array = new T[count];   // (*) 
       is2.CopyTo(array, 0);    // (*) 
       array.CopyTo(this._items, index); // (*) 
      } 
      this._size += count; 
     } 
    } 
    else 
    { 
     using (IEnumerator<T> enumerator = collection.GetEnumerator()) 
     { 
      while (enumerator.MoveNext()) 
      { 
       this.Insert(index++, enumerator.Current); 
      } 
     } 
    } 
    this._version++; 
} 

private T[] _items; 

Người ta có thể tranh luận rằng sự đơn giản của giao diện (chỉ có một tình trạng quá tải của InsertRange) biện minh cho các chi phí thực hiện thời gian chạy kiểu cheching và đúc. Nhưng điều gì có thể là lý do đằng sau 3 dòng tôi đã chỉ ra với (*)? Tôi nghĩ rằng nó có thể được viết lại để thay thế nhanh hơn:

is2.CopyTo(this._items, index); 

Bạn có thấy bất kỳ lý do để không sử dụng này đơn giản và dường như thay thế nhanh hơn?

Edit:

Cảm ơn câu trả lời. Vì vậy, ý kiến ​​đồng thuận là đây là biện pháp bảo vệ chống lại việc thu thập dữ liệu đầu vào thực hiện CopyTo theo cách bị lỗi/độc hại. Đối với tôi, có vẻ như quá mức cần phải trả giá 1) kiểm tra kiểu thời gian chạy 2) phân bổ động của mảng tạm 3) tăng gấp đôi thao tác sao chép, khi tất cả điều này có thể đã được lưu bằng cách xác định 2 hoặc một vài quá tải của InsertRange , một người nhận được IEnumerable như bây giờ, người thứ hai nhận được List<T>, thứ ba nhận được T[]. Hai phiên bản sau có thể đã được triển khai để chạy nhanh gấp hai lần trong trường hợp hiện tại.

Chỉnh sửa 2:

tôi đã thực hiện một danh sách nhanh lớp, giống với Liệt kê, ngoại trừ việc nó cũng cung cấp một tình trạng quá tải của AddRange mà phải mất một T [] lập luận. Quá tải này không cần xác minh loại động và sao chép hai phần tử. Tôi đã làm hồ sơ này FastList.AddRange chống List.AddRange bằng cách thêm mảng 4-byte 1000 lần vào một danh sách mà ban đầu là emtpy. Thực hiện của tôi đánh bại tốc độ của List.AddRange tiêu chuẩn với một hệ số 9 (chín!). List.AddRange mất khoảng 5% thời gian chạy trong một trong các kịch bản sử dụng quan trọng của ứng dụng của chúng tôi, thay thế Danh sách với một lớp cung cấp AddRange nhanh hơn có thể cải thiện thời gian chạy ứng dụng lên 4%.

+0

@shojtsy: đảm bảo bạn nắm bắt chỉnh sửa 2 :) –

+0

Bạn đã sử dụng trường hợp xấu nhất cho thử nghiệm của mình. Hãy thử chèn một 'int' duy nhất với' Chèn' thay vì mảng 4 byte với 'ChènRange' và bạn sẽ nhận được nhiều dấu vết hơn. –

+0

Kịch bản thử nghiệm là đại diện cho việc sử dụng thực tế trong ứng dụng của tôi. Danh sách này là một luồng byte trong đó các mảng 4-10 byte được nối nhiều lần. Tôi hiểu rằng việc chuyển một Mảng đơn giản tới AddRange là nơi mà các hình phạt về hiệu suất của việc triển khai chuẩn được hiển thị rõ nhất. Đó chính là ý của tôi. – shojtsy

Trả lời

11

Chúng đang ngăn việc triển khai ICollection<T> từ việc truy cập các chỉ mục của danh sách đích bên ngoài giới hạn chèn. Việc thực hiện ở trên dẫn đến một IndexOutOfBoundsException nếu việc triển khai lỗi (hoặc "thao túng") của CopyTo được gọi.

Hãy nhớ rằng T[].CopyTo được thực hiện hoàn toàn theo nghĩa đen là memcpy, vì vậy chi phí hoạt động của việc thêm dòng đó là phút. Khi bạn có một chi phí thấp như tăng thêm sự an toàn cho một số lượng lớn các cuộc gọi, bạn cũng có thể làm như vậy.

Chỉnh sửa: Phần tôi thấy lạ là thực tế là cuộc gọi đến ICollection<T>.CopyTo (sao chép vào mảng tạm thời) không xảy ra ngay lập tức sau cuộc gọi đến EnsureCapacity. Nếu nó được di chuyển đến vị trí đó, thì hãy theo dõi bất kỳ synchronous exception danh sách nào sẽ không thay đổi. Như-là, điều kiện đó chỉ giữ nếu chèn xảy ra ở cuối danh sách. Lý do ở đây là:

  • Mọi phân bổ cần thiết xảy ra trước khi thay đổi các yếu tố danh sách.
  • Các cuộc gọi đến Array.Copy không thể thất bại vì
    • Bộ nhớ đã được phân bổ
    • Các giới hạn đã được kiểm tra
    • Các loại yếu tố của nguồn và đích mảng phù hợp
    • Không có "copy constructor "được sử dụng như trong C++ - chỉ là một memcpy
  • Các mục duy nhất có thể ném ngoại lệ là cuộc gọi bên ngoài tới ICollection.CopyTo và phân bổ cần thiết để thay đổi kích thước danh sách và phân bổ mảng tạm thời. Nếu cả ba điều này xảy ra trước khi di chuyển các phần tử để chèn, giao dịch để thay đổi danh sách không thể ném một ngoại lệ đồng bộ.
  • Lưu ý cuối cùng: Địa chỉ này hoàn toàn ngoại lệ - lý do trên không thực hiện không thêm chủ đề an toàn.

Chỉnh sửa 2 (phản hồi chỉnh sửa của OP): Bạn đã lược tả điều này chưa? Bạn đang đưa ra một số tuyên bố táo bạo rằng Microsoft nên chọn một API phức tạp hơn, vì vậy bạn nên chắc chắn rằng bạn đã đúng trong các xác nhận rằng phương pháp hiện tại là chậm. Tôi chưa bao giờ gặp vấn đề với hiệu suất của InsertRange và tôi khá chắc chắn rằng bất kỳ sự cố hiệu suất nào mà một người nào đó đối mặt với nó sẽ được giải quyết tốt hơn với thiết kế lại thuật toán thay vì thực hiện lại danh sách động.Chỉ cần để bạn không đưa tôi như là khắc nghiệt một cách tiêu cực, giữ những điều sau:

  • tôi không muốn không thể chịu được mọi người trong nhóm dev của tôi rằng muốn reinvent the square wheel.
  • I chắc chắn muốn mọi người trong nhóm của tôi quan tâm đến các vấn đề hiệu suất tiềm năng và đặt câu hỏi về các tác dụng phụ mà mã của họ có thể có. Điểm này thắng khi có mặt - nhưng miễn là mọi người đang đặt câu hỏi, tôi sẽ thúc đẩy họ chuyển câu hỏi của họ thành câu trả lời chắc chắn. Nếu bạn có thể cho tôi thấy rằng một ứng dụng có được một lợi thế đáng kể thông qua những gì ban đầu dường như là một ý tưởng tồi, thì đó chỉ là cách mọi thứ đi đôi khi.
+0

Trình cấp phát CLR cực kỳ nhanh, cộng với mảng tạm thời có khả năng được thu thập trước khi đến Gen 1 để nó không gây ra vấn đề gì. –

2

Đó là một câu hỏi hay, tôi đang cố gắng tìm ra lý do. Không có gợi ý nào trong Nguồn tham chiếu. Một khả năng là chúng cố gắng tránh một vấn đề khi lớp thực hiện các đối tượng phương thức ICollection <> .CopyTo() chống lại việc sao chép tới chỉ mục bắt đầu khác 0 hoặc là một biện pháp bảo mật, ngăn không cho bộ sưu tập gây rối với các phần tử mảng nó không nên có quyền truy cập vào.

Một số khác là đây là biện pháp đối chiếu khi bộ sưu tập được sử dụng theo cách không an toàn. Nếu một mục đã được thêm vào bộ sưu tập bởi một luồng khác, nó sẽ là phương thức 'CopyTo() của lớp sưu tập không thành công, không phải mã của Microsoft. Người thích hợp sẽ nhận cuộc gọi dịch vụ.

Đây không phải là những giải thích tuyệt vời.

+0

Các thành viên của 'Danh sách ' được tuyên bố rõ ràng là không an toàn chỉ. Miễn là 'List ' và tập hợp các mục được chèn vào được đồng bộ hóa theo cách thủ công, các lệnh gọi đến 'CopyTo' không bao giờ có thể xảy ra theo cách nguy hiểm. –

+1

Có, nhưng đó không phải là vấn đề. Vấn đề là: ai nhận được cuộc gọi điện thoại khi nó nổ tung. Một mối quan tâm thực sự đối với một công ty như Microsoft. Hotfix này là một ví dụ tốt, nó thực sự là một ngoại lệ được kích hoạt bởi mã khách hàng quên khóa: http://support.microsoft.com/kb/831730 –

+0

Trong trường hợp trên, không phải mã gốc cũng như thay đổi được đề xuất đều là chủ đề - an toàn hơn cái kia. Một điều lạ là thực tế là cuộc gọi đến 'ICollection .CopyTo' không được đặt ngay sau khi cuộc gọi đến 'EnsureCapacity' - nếu nó được sau đó khi ngoại lệ đồng bộ, danh sách sẽ không thay đổi bởi thao tác. –

0

Có vấn đề với giải pháp của bạn nếu bạn nghĩ về nó trong một phút, nếu bạn thay đổi mã theo cách đó về cơ bản bạn sẽ đưa bộ sưu tập được thêm quyền truy cập vào cơ sở dữ liệu nội bộ.Đây không phải là một ý tưởng tốt, ví dụ nếu tác giả của cơ sở hạ tầng Danh sách tìm ra cấu trúc cơ bản tốt hơn để lưu trữ dữ liệu hơn một mảng thì không có cách nào thay đổi việc thực hiện Danh sách vì tất cả các bộ sưu tập đang mong đợi một mảng vào chức năng CopyTo. Về bản chất, bạn sẽ củng cố việc thực hiện lớp List, mặc dù lập trình hướng đối tượng cho chúng ta biết rằng việc thực hiện nội bộ của một datastructure phải là một thứ có thể thay đổi mà không phá vỡ mã khác.

+0

-1: Anh ấy đang nói về việc triển khai nội bộ hiện tại. Việc triển khai đó đã dựa vào một đối tượng đang triển khai 'ICollection ' để thực hiện đúng cách thành viên 'CopyTo' của giao diện đó. Lựa chọn được đề xuất không thêm cũng như loại bỏ khỏi danh sách các giả định đó. –

+0

Đó là sự thật nhưng nó không thay đổi thực tế là đề nghị của ông vẫn phá vỡ đóng gói nếu cấu trúc dữ liệu nội bộ Danh sách cũng như dựa vào việc triển khai đúng bộ sưu tập đến. Không có gì đảm bảo rằng bộ sưu tập đang được thêm vào danh sách có thực hiện tốt hoặc nhanh chóng CopyTo, thậm chí tệ hơn nó có thể có một thực hiện mà bây giờ truy cập vào dữ liệu nội bộ của danh sách mà nó sẽ không có và không nên có và có thể sử dụng để lấy cắp dữ liệu mà nó không có quyền truy cập. Tôi tin rằng quan điểm của tôi vẫn đứng vững. –

+0

Tuyên bố ban đầu của bạn là chính xác, nhưng lý do bạn đưa ra trong đoạn 2 và 3 sẽ không phải là lý do để đi đến kết luận đó. Ngoài ra, bạn phải làm việc trên giả định rằng việc thực hiện 'ICollection .CopyTo' là đúng - đó là lý do tại sao giao diện chuẩn tồn tại.Lý do đơn giản là một mục tiêu chính trong các VM được quản lý là nhận được một ngoại lệ vượt quá giới hạn cho việc truy cập bộ nhớ bất hợp pháp, và đây là một cách rẻ tiền để làm như vậy. –

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