Có tài liệu nào so sánh / đối chiếu việc triển khai thư viện chuẩn C ++ không? [đóng cửa]


16

.

Có tài liệu nào ở bất cứ nơi nào tóm tắt sự khác biệt về hiệu suất và đặc biệt là việc sử dụng bộ nhớ, giữa các triển khai thư viện chuẩn C ++ khác nhau không? Chi tiết của một số triển khai được NDA bảo vệ, nhưng so sánh giữa STLport so với libstdc ++ so với libc ++ so với MSVC / Dinkumware (so với EASTL?) Có vẻ như nó sẽ vô cùng hữu ích.

Cụ thể, tôi đang tìm câu trả lời cho các câu hỏi như:

  • Bao nhiêu bộ nhớ trên được liên kết với các thùng chứa tiêu chuẩn?
  • Những container nào, nếu có, thực hiện phân bổ động chỉ bằng cách khai báo?
  • Có std :: chuỗi làm copy-on-write không? Tối ưu hóa chuỗi ngắn? Dây thừng?
  • Có std :: deque sử dụng bộ đệm vòng hay nó là crap?

Tôi đã có ấn tượng dequeluôn được thực hiện trong STL với một vectơ.
Tetrad

@Tetrad: Cho đến vài tuần trước tôi cũng vậy, nhưng sau đó tôi đọc nó thường được thực hiện bởi một cấu trúc giống như dây thừng - và đó dường như là những gì trong STLport.

STL có một dự thảo làm việc mở , có thể được sử dụng để tìm thông tin liên quan đến các cấu trúc dữ liệu khác nhau (cả tuần tự và liên kết), các thuật toán và các lớp trợ giúp được triển khai. Tuy nhiên, dường như là trường hợp chi phí bộ nhớ được triển khai cụ thể, thay vì xác định cụ thể.
Thomas Russell

3
@Duck: Phát triển trò chơi là nơi duy nhất tôi biết rằng thường xuyên sử dụng các tính năng C ++ cấp cao nhưng vẫn cần theo dõi phân bổ bộ nhớ một cách tỉ mỉ vì nó chạy trên các hệ thống không có bộ nhớ ảo. Mỗi câu trả lời trên SO sẽ là "không tối ưu hóa sớm, STL vẫn ổn, hãy sử dụng nó!" - 50% câu trả lời ở đây cho đến nay là vậy - nhưng bài kiểm tra của Maik rõ ràng cho thấy mối quan tâm lớn đối với các trò chơi muốn sử dụng std :: map, và sự nhầm lẫn của Tetrad và của tôi về các triển khai std :: deque phổ biến tương tự.

2
@Joe Wreschnig Tôi không thực sự muốn bỏ phiếu để đóng vì tôi quan tâm đến kết quả của việc này. : p
Vịt Cộng sản

Câu trả lời:


6

Trong trường hợp bạn không tìm thấy biểu đồ so sánh như vậy, phương án thay thế là đưa một bộ cấp phát riêng vào các lớp STL được đề cập và thêm một số ghi nhật ký.

Việc triển khai mà tôi đã thử nghiệm (VC 8.0) không sử dụng cấp phát bộ nhớ chỉ bằng cách khai báo chuỗi / vectơ / deque, nhưng đối với nó có liệt kê và ánh xạ. Chuỗi có tối ưu hóa chuỗi ngắn, vì việc thêm 3 ký tự không kích hoạt phân bổ. Đầu ra được thêm vào bên dưới mã.

// basic allocator implementation used from here
// http://www.codeguru.com/cpp/cpp/cpp_mfc/stl/article.php/c4079

#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include <deque>
#include <list>
#include <map>

template <class T> class my_allocator;

// specialize for void:
template <> 
class my_allocator<void> 
{
public:
    typedef void*       pointer;
    typedef const void* const_pointer;
    // reference to void members are impossible.
    typedef void value_type;
    template <class U> 
    struct rebind 
    { 
        typedef my_allocator<U> other; 
    };
};

#define LOG_ALLOC_SIZE(call, size)      std::cout << "  " << call << "  " << std::setw(2) << size << " byte" << std::endl

template <class T> 
class my_allocator 
{
public:
    typedef size_t    size_type;
    typedef ptrdiff_t difference_type;
    typedef T*        pointer;
    typedef const T*  const_pointer;
    typedef T&        reference;
    typedef const T&  const_reference;
    typedef T         value_type;
    template <class U> 
    struct rebind 
    { 
        typedef my_allocator<U> other; 
    };

    my_allocator() throw() : alloc() {}
    my_allocator(const my_allocator&b) throw() : alloc(b.alloc) {}

    template <class U> my_allocator(const my_allocator<U>&b) throw() : alloc(b.alloc) {}
    ~my_allocator() throw() {}

    pointer       address(reference x) const                    { return alloc.address(x); }
    const_pointer address(const_reference x) const              { return alloc.address(x); }

    pointer allocate(size_type s, 
               my_allocator<void>::const_pointer hint = 0)      { LOG_ALLOC_SIZE("my_allocator::allocate  ", s * sizeof(T)); return alloc.allocate(s, hint); }
    void deallocate(pointer p, size_type n)                     { LOG_ALLOC_SIZE("my_allocator::deallocate", n * sizeof(T)); alloc.deallocate(p, n); }

    size_type max_size() const throw()                          { return alloc.max_size(); }

    void construct(pointer p, const T& val)                     { alloc.construct(p, val); }
    void destroy(pointer p)                                     { alloc.destroy(p); }

    std::allocator<T> alloc;
};

int main(int argc, char *argv[])
{

    {
        typedef std::basic_string<char, std::char_traits<char>, my_allocator<char> > my_string;

        std::cout << "===============================================" << std::endl;
        std::cout << "my_string ctor start" << std::endl;
        my_string test;
        std::cout << "my_string ctor end" << std::endl;
        std::cout << "my_string add 3 chars" << std::endl;
        test = "abc";
        std::cout << "my_string add a huge number of chars chars" << std::endl;
        test += "d df uodfug ondusgp idugnösndögs ifdögsdoiug ösodifugnösdiuödofu odsugöodiu niu od unoudö n nodsu nosfdi un abc";
        std::cout << "my_string copy" << std::endl;
        my_string copy = test;
        std::cout << "my_string copy on write test" << std::endl;
        copy[3] = 'X';
        std::cout << "my_string dtors start" << std::endl;
    }

    {
        std::cout << std::endl << "===============================================" << std::endl;
        std::cout << "vector ctor start" << std::endl;
        std::vector<int, my_allocator<int> > v;
        std::cout << "vector ctor end" << std::endl;
        for(int i = 0; i < 5; ++i)
        {
            v.push_back(i);
        }
        std::cout << "vector dtor starts" << std::endl;
    }

    {
        std::cout << std::endl << "===============================================" << std::endl;
        std::cout << "deque ctor start" << std::endl;
        std::deque<int, my_allocator<int> > d;
        std::cout << "deque ctor end" << std::endl;
        for(int i = 0; i < 5; ++i)
        {
            std::cout << "deque insert start" << std::endl;
            d.push_back(i);
            std::cout << "deque insert end" << std::endl;
        }
        std::cout << "deque dtor starts" << std::endl;
    }

    {
        std::cout << std::endl << "===============================================" << std::endl;
        std::cout << "list ctor start" << std::endl;
        std::list<int, my_allocator<int> > l;
        std::cout << "list ctor end" << std::endl;
        for(int i = 0; i < 5; ++i)
        {
            std::cout << "list insert start" << std::endl;
            l.push_back(i);
            std::cout << "list insert end" << std::endl;
        }
        std::cout << "list dtor starts" << std::endl;
    }

    {
        std::cout << std::endl << "===============================================" << std::endl;
        std::cout << "map ctor start" << std::endl;
        std::map<int, float, std::less<int>, my_allocator<std::pair<const int, float> > > m;
        std::cout << "map ctor end" << std::endl;
        for(int i = 0; i < 5; ++i)
        {
            std::cout << "map insert start" << std::endl;
            std::pair<int, float> a(i, (float)i);
            m.insert(a);
            std::cout << "map insert end" << std::endl;
        }
        std::cout << "map dtor starts" << std::endl;
    }

    return 0;
}

Cho đến nay, VC8 và STLPort 5.2 đã được thử nghiệm, đây là so sánh (bao gồm trong thử nghiệm: chuỗi, vectơ, deque, danh sách, bản đồ)

                    Allocation on declare   Overhead List Node      Overhead Map Node

VC8                 map, list               8 Byte                  16 Byte
STLPort 5.2 (VC8)   deque                   8 Byte                  16 Byte
Paulhodge's EASTL   (none)                  8 Byte                  16 Byte

Chuỗi đầu ra VC8 / vector / deque / list / map:

===============================================
my_string ctor start
my_string ctor end
my_string add 3 chars
my_string add a huge number of chars chars
  my_allocator::allocate    128 byte
my_string copy
  my_allocator::allocate    128 byte
my_string copy on write test
my_string dtors start
  my_allocator::deallocate  128 byte
  my_allocator::deallocate  128 byte

===============================================
vector ctor start
vector ctor end
  my_allocator::allocate     4 byte
  my_allocator::allocate     8 byte
  my_allocator::deallocate   4 byte
  my_allocator::allocate    12 byte
  my_allocator::deallocate   8 byte
  my_allocator::allocate    16 byte
  my_allocator::deallocate  12 byte
  my_allocator::allocate    24 byte
  my_allocator::deallocate  16 byte
vector dtor starts
  my_allocator::deallocate  24 byte

===============================================
deque ctor start
deque ctor end
deque insert start
  my_allocator::allocate    32 byte
  my_allocator::allocate    16 byte
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
  my_allocator::allocate    16 byte
deque insert end
deque dtor starts
  my_allocator::deallocate  16 byte
  my_allocator::deallocate  16 byte
  my_allocator::deallocate  32 byte

===============================================
list ctor start
  my_allocator::allocate    12 byte
list ctor end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list dtor starts
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte

===============================================
map ctor start
  my_allocator::allocate    24 byte
map ctor end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map dtor starts
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte

STLPort 5.2. đầu ra được biên dịch với VC8

===============================================
my_string ctor start
my_string ctor end
my_string add 3 chars
my_string add a huge number of chars chars
  my_allocator::allocate    115 byte
my_string copy
  my_allocator::allocate    115 byte
my_string copy on write test
my_string dtors start
  my_allocator::deallocate  115 byte
  my_allocator::deallocate  115 byte

===============================================
vector ctor start
vector ctor end
  my_allocator::allocate     4 byte
  my_allocator::deallocate   0 byte
  my_allocator::allocate     8 byte
  my_allocator::deallocate   4 byte
  my_allocator::allocate    16 byte
  my_allocator::deallocate   8 byte
  my_allocator::allocate    32 byte
  my_allocator::deallocate  16 byte
vector dtor starts
  my_allocator::deallocate  32 byte

===============================================
deque ctor start
  my_allocator::allocate    32 byte
  my_allocator::allocate    128 byte
deque ctor end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque dtor starts
  my_allocator::deallocate  128 byte
  my_allocator::deallocate  32 byte

===============================================
list ctor start
list ctor end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list dtor starts
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte

===============================================
map ctor start
map ctor end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map dtor starts
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte

Kết quả EASTL , không có deque có sẵn

===============================================
my_string ctor start
my_string ctor end
my_string add 3 chars
  my_allocator::allocate     9 byte
my_string add a huge number of chars chars
  my_allocator::allocate    115 byte
  my_allocator::deallocate   9 byte
my_string copy
  my_allocator::allocate    115 byte
my_string copy on write test
my_string dtors start
  my_allocator::deallocate  115 byte
  my_allocator::deallocate  115 byte

===============================================
vector ctor start
vector ctor end
  my_allocator::allocate     4 byte
  my_allocator::allocate     8 byte
  my_allocator::deallocate   4 byte
  my_allocator::allocate    16 byte
  my_allocator::deallocate   8 byte
  my_allocator::allocate    32 byte
  my_allocator::deallocate  16 byte
vector dtor starts
  my_allocator::deallocate  32 byte

===============================================
list ctor start
list ctor end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list dtor starts
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte

===============================================
map ctor start
map ctor end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map dtor starts
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte

Điều này rất hữu ích để có được các chi tiết của phân bổ cơ bản, nhưng không may cho chúng ta không biết gì về hiệu năng bộ đệm và chi phí dự kiến.

@Joe đúng, thật khó để giải quyết tất cả các câu hỏi của bạn trong một câu trả lời. Tôi không chắc chính xác ý bạn là gì với "phí" và hơn thế nữa, so với cái gì? Tôi nghĩ rằng trên đầu bạn có nghĩa là tiêu thụ bộ nhớ.
Maik

Bằng cách "trên cao" tôi có nghĩa là kích thước của các phiên bản trống của các cấu trúc và tất cả các trình lặp được liên kết của chúng và cách các phần tử phức tạp hơn xử lý phân bổ - ví dụ: std :: liệt kê nội bộ nhiều hơn một nút tại một thời điểm, hoặc tôi trả chi phí phân bổ cơ sở cho mỗi nút, vv?

1
Câu hỏi không quá nhiều "Hãy thực hiện so sánh này" vì "đâu là tài nguyên cho so sánh này" - Tôi không nghĩ SO là một nơi tốt để "duy trì" nó. Có lẽ bạn nên bắt đầu đưa nó lên một trang web Google hoặc wiki hoặc một cái gì đó.

1
Bây giờ tôi cũng ở đây: p Tôi không quan tâm lắm đến việc chuyển nó sang một trang khác, tôi chỉ quan tâm đến kết quả.
Maik

8

std::stringkhông làm bản sao trên viết. CoW từng là một tối ưu hóa, nhưng ngay khi nhiều luồng vào hình ảnh, nó vượt quá sự bi quan - nó có thể làm chậm mã bởi các yếu tố lớn. Thật tệ khi C ++ 0x Standard chủ động cấm nó như một chiến lược thực hiện. Không chỉ vậy, mà sự cho phép std::stringvới việc lặp đi lặp lại các tham số lặp và tham chiếu ký tự có nghĩa là "viết" cho std::stringhầu hết mọi thao tác.

Tối ưu hóa chuỗi ngắn là khoảng 6 ký tự, tôi tin hoặc một cái gì đó trong khu vực đó. Dây thừng không được phép - std::stringphải lưu trữ bộ nhớ liền kề cho c_str()chức năng. Về mặt kỹ thuật, bạn có thể duy trì cả một chuỗi liền kề và một sợi dây trong cùng một lớp, nhưng không ai từng làm điều đó. Hơn nữa, từ những gì tôi biết về dây thừng, làm cho chúng an toàn khi sử dụng sợi chỉ sẽ cực kỳ chậm - có thể xấu hoặc tệ hơn CoW.

Không có container nào cấp phát bộ nhớ bằng cách khai báo trong STL hiện đại. Các thùng chứa dựa trên nút như danh sách và bản đồ được sử dụng để làm như vậy nhưng bây giờ chúng có tối ưu hóa kết thúc nhúng và không cần nó. Việc thực hiện tối ưu hóa gọi là "swaptimization" là điều thông thường khi bạn trao đổi với một container rỗng. Xem xét:

std::vector<std::string> MahFunction();
int main() {
    std::vector<std::string> MahVariable;
    MahFunction().swap(MahVariable);
}

Tất nhiên, trong C ++ 0x thì điều này là dư thừa, nhưng trong C ++ 03 thì khi điều này được sử dụng phổ biến, nếu MahVariable phân bổ bộ nhớ khi khai báo thì nó làm giảm hiệu quả. Tôi biết một thực tế rằng điều này đã được sử dụng để phân bổ lại các container nhanh hơn như vectortrong MSVC9 STL đã loại bỏ nhu cầu sao chép các yếu tố.

dequesử dụng một cái gì đó được gọi là một danh sách liên kết không được kiểm soát. Về cơ bản, nó là một danh sách các mảng, thường có kích thước cố định trong nút. Như vậy, đối với hầu hết các mục đích sử dụng, nó vẫn giữ được lợi ích của cả hai cấu trúc dữ liệu - truy cập liền kề và loại bỏ O (1) được khấu hao và có thể thêm vào cả mặt trước và mặt sau hiệu lực của trình lặp tốt hơn vector. dequekhông bao giờ có thể được thực hiện bởi vectơ vì nó phức tạp về thuật toán và đảm bảo tính hợp lệ của trình lặp.

Bao nhiêu bộ nhớ có liên quan? Chà, thành thật mà nói, đó là một câu hỏi không đáng để hỏi. Các thùng chứa STL được thiết kế để hoạt động hiệu quả và nếu bạn muốn sao chép chức năng của chúng, bạn sẽ kết thúc với thứ gì đó hoạt động kém hơn hoặc ở cùng một chỗ một lần nữa. Bằng cách biết cấu trúc dữ liệu cơ bản của chúng, bạn có thể biết chi phí bộ nhớ mà chúng sử dụng, cho hoặc nhận và nó sẽ chỉ nhiều hơn thế vì một lý do chính đáng, chẳng hạn như tối ưu hóa chuỗi nhỏ.


"Thật tệ khi Tiêu chuẩn C ++ 0x chủ động cấm nó như một chiến lược thực hiện." Và họ cấm nó bởi vì các triển khai trước đó đã sử dụng nó, hoặc cố gắng sử dụng nó. Bạn dường như sống trong một thế giới nơi mọi người đều sử dụng STL được triển khai tối ưu nhất mọi lúc. Câu trả lời này hoàn toàn không hữu ích.

Tôi cũng tò mò những thuộc tính nào của std :: deque mà bạn nghĩ ngăn chặn lưu trữ bên dưới liền kề - các trình lặp chỉ có giá trị sau khi xóa ở đầu / cuối, không phải ở giữa cũng như sau bất kỳ chèn nào, có thể dễ dàng thực hiện với một vectơ. Và sử dụng bộ đệm tròn dường như đáp ứng tất cả các đảm bảo thuật toán - chèn và xóa O (1) ở cuối, xóa O (n) ở giữa.

3
@Joe: Tôi nghĩ rằng CoW đã được ghi nhận là một điều xấu kể từ cuối những năm 90. Có các triển khai chuỗi đã sử dụng nó - đặc biệt là CString- nhưng điều đó không có nghĩa là std::stringthời gian đã làm. Bạn không cần phải sử dụng các triển khai STL mới nhất và lớn nhất cho việc đó. msdn.microsoft.com/en-us/l Library / 22a9t119.aspx nói "Nếu một phần tử được chèn ở phía trước, tất cả các tham chiếu vẫn còn hiệu lực". Không chắc chắn về cách bạn dự định thực hiện điều đó với bộ đệm tròn, vì bạn sẽ cần thay đổi kích thước khi nó đầy.
DeadMG


Tôi chắc chắn sẽ không bảo vệ COW như một kỹ thuật triển khai, nhưng tôi cũng không ngây thơ về việc tần suất phần mềm tiếp tục được thực hiện bằng cách sử dụng các kỹ thuật kém sau khi chúng được xác định là kém. Ví dụ, thử nghiệm của Maik ở trên cho thấy một stdlib hiện đại được phân bổ trên khai báo. Cảm ơn con trỏ về tính hợp lệ tham chiếu deque. (Đối với nitpick, một vectơ có thể đáp ứng tất cả các đảm bảo về tính vô hiệu của trình lặp và độ phức tạp thuật toán; yêu cầu đó là không.) Nếu có bất cứ điều gì, tôi thấy điều này là cần thêm cho một tài liệu như câu hỏi của tôi yêu cầu.

2

Câu hỏi không quá nhiều "Hãy làm so sánh này" là "tài nguyên cho sự so sánh này ở đâu"

Nếu đó thực sự là câu hỏi của bạn (chắc chắn không phải là những gì bạn đã nói trong văn bản câu hỏi thực tế của bạn, kết thúc bằng 4 câu hỏi, không ai trong số đó hỏi bạn có thể tìm thấy tài nguyên ở đâu), thì câu trả lời chỉ đơn giản là:

Không có cái nào cả.

Phần lớn các lập trình viên C ++ không phải quan tâm nhiều đến chi phí hoạt động của các cấu trúc thư viện tiêu chuẩn, hiệu năng bộ đệm của họ ( dù sao cũng phụ thuộc vào trình biên dịch cao ) hoặc đại loại thế. Chưa kể, bạn thường không được chọn triển khai thư viện tiêu chuẩn của mình; bạn sử dụng những gì đi kèm với trình biên dịch của bạn. Vì vậy, ngay cả khi nó làm một số điều khó chịu, các tùy chọn cho các lựa chọn thay thế bị hạn chế.

Tất nhiên có những lập trình viên quan tâm đến loại điều này. Nhưng tất cả họ đã thề sử dụng thư viện tiêu chuẩn từ lâu.

Vì vậy, bạn có một nhóm lập trình viên đơn giản là không quan tâm. Và một nhóm lập trình viên khác sẽ quan tâm nếu họ đang sử dụng nó, nhưng vì họ không sử dụng nó, nên họ không quan tâm. Vì không ai quan tâm đến nó, nên không có thông tin thực sự về loại điều này. Có những bản vá thông tin không chính thức ở đây và ở đó (C ++ hiệu quả có một phần về triển khai chuỗi std :: và sự khác biệt lớn giữa chúng), nhưng không có gì toàn diện. Và chắc chắn không có gì được cập nhật.


Câu trả lời đầu cơ. +1 có lẽ đúng, -1 không có cách nào chứng minh điều đó.
deceleratedcaviar

Tôi đã thấy nhiều so sánh rất hay và chi tiết trong quá khứ, nhưng tất cả chúng đều lỗi thời. Bất cứ điều gì trước khi giới thiệu di chuyển là khá nhiều không liên quan ngày nay.
Peter - Unban Robert Harvey
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.