首页 > 编程语言 >C# List<T>.Capacity 深入剖析

C# List<T>.Capacity 深入剖析

时间:2022-10-16 18:56:46浏览次数:38  
标签:const C# items List C++ Oldcapacity Capacity size

引子

之前在网络上看到,C++ 中若 Vector 在初始化或者使用前,指定 Capacity 大小的话,会减少由于新增元素导致超出 Capacity 时的元素拷贝。(以下 源码均为 MSVC C++ 编译器下)

void test_with_no_reserve(size_t loop_count) {
  std::vector<std::string> aa{};

  for (size_t i = 0; i < loop_count; ++i) {
    aa.emplace_back(std::to_string(i));
  }
}

void test_with_capacity(size_t loop_count) {
  std::vector<std::string> a{};
  a.reserve(loop_count);

  for (size_t i = 0; i < loop_count; ++i) {
    a.emplace_back(std::to_string(i));
  }
}

两者在执行效率上 1000 * 10000 loop_count
test_with_no_reserve: 24486 ms
test_with_capacity: 11989 ms

可以看下 Vector Capacity 的自增算法如下所示:

_CONSTEXPR20 size_type _Calculate_growth(const size_type _Newsize) const {
        // given _Oldcapacity and _Newsize, calculate geometric growth
        const size_type _Oldcapacity = capacity();
        const auto _Max              = max_size();
        if (_Oldcapacity > _Max - _Oldcapacity / 2) {
            return _Max; // geometric growth would overflow
        }
        const size_type _Geometric = _Oldcapacity + _Oldcapacity / 2;
        if (_Geometric < _Newsize) {
            return _Newsize; // geometric growth would be insufficient
        }
        return _Geometric; // geometric growth is sufficient
    }

在 C++ 中,Vector 提供了 Reserve 这个方法。(以下 C++ 源码均为 MSVC 下的源码),用于初始 Vector Capacity 的设置。在 C++11 之前网上搜索 push_back 和 emplace_back 时可能都会推荐后者,其实上 C++11 之后,两者其实都是调用 后者的方法 emplace_back。

_CONSTEXPR20 void _Reallocate_exactly(const size_type _Newcapacity) {
        // set capacity to _Newcapacity (without geometric growth), provide strong guarantee
        auto& _Al         = _Getal();
        auto& _My_data    = _Mypair._Myval2;
        pointer& _Myfirst = _My_data._Myfirst;
        pointer& _Mylast  = _My_data._Mylast;
        const auto _Size = static_cast<size_type>(_Mylast - _Myfirst);
        const pointer _Newvec = _Al.allocate(_Newcapacity);
        _TRY_BEGIN
        if constexpr (is_nothrow_move_constructible_v<_Ty> || !is_copy_constructible_v<_Ty>) {
            _Uninitialized_move(_Myfirst, _Mylast, _Newvec, _Al);
        } else {
            _Uninitialized_copy(_Myfirst, _Mylast, _Newvec, _Al);
        }
        _CATCH_ALL
        _Al.deallocate(_Newvec, _Newcapacity);
        _RERAISE;
        _CATCH_END
        _Change_array(_Newvec, _Size, _Newcapacity);
    }

_Uninitialized_move(_Myfirst, _Mylast, _Newvec, _Al);
_Uninitialized_copy(_Myfirst, _Mylast, _Newvec, _Al);
这两个的区别简述一下,一个是调用了 std::move 也就是对象的移动拷贝构造函数,一个是调用了 对象的拷贝构造函数。
std::move 只是相当于转移了对象的使用权,并不重新创建对象,所以会比 copy_constructor 高效。
Move declare: _ty(_ty &&) (右值引用)
copy constructor declare: _ty(const _ty&)

跑偏到了C++, 现在开始讲 C# 命名空间 Collections.Generic 下的 List,List 可以说是大家用的最多的Collection 中的一个泛型类。我们还是从 Capacity 开始。
Path 在 dotnet/runtime repo下 src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs

C# List.Capacity

 public int Capacity {
            get {
                Contract.Ensures(Contract.Result<int>() >= 0);
                return _items.Length;
            }
            set {
                if (value < _size) {
                    ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity);
                }
                Contract.EndContractBlock();
 
                if (value != _items.Length) {
                    if (value > 0) {
                        T[] newItems = new T[value];
                        if (_size > 0) {
                            Array.Copy(_items, 0, newItems, 0, _size);
                        }
                        _items = newItems;
                    }
                    else {
                        _items = _emptyArray;
                    }
                }
            }
        }

其中 Array.Copy 实际上是调用了 C++ 底层的 memmove 方法

List.Add Capacity 自增算法

 private void EnsureCapacity(int min) {
            if (_items.Length < min) {
                int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length * 2;
                // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
                // Note that this check works even when _items.Length overflowed thanks to the (uint) cast
                if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
                if (newCapacity < min) newCapacity = min;
                Capacity = newCapacity;
            }
        }

与 C++ 自增算法的对比

C++:const size_type _Geometric = _Oldcapacity + _Oldcapacity / 2;

C# :DefaultCapacity : 2 * _items.Length;
C# 看来还是比较粗狂的,对于分配空间上,C++ 的话比C#,每次在自增超出空间的分配上默认会多 50%。

Capacity 代码中的分析

T[] newItems = new T[value];
这里我们知道,创建的是一个新的数组,大小就是为 Capacity 的大小,并且为引用类型,分配在堆上,由GC管理其空间的释放。
那么就是比如我有一个1000大小的数组,DefaultCapacity = 4;
那么其需要进行 Array.Copy 的次数为 2^10 = 1024
4->8->16->32->64->128->256->512->1024 八次。
若事先指定了 Capacity 则只有在 初始化这个 List 时,申请了这些空间。

标签:const,C#,items,List,C++,Oldcapacity,Capacity,size
From: https://www.cnblogs.com/xiyin/p/16796778.html

相关文章

  • Docker安装MongoDB并使用Navicat连接
    MongoDB简介:MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。是一个介于关系数据库和非关系数据库之间......
  • #yyds干货盘点# LeetCode 热题 HOT 100:二叉树的中序遍历
    题目:给定一个二叉树的根节点root,返回它的中序 遍历。 示例1:输入:root=[1,null,2,3]输出:[1,3,2]示例2:输入:root=[]输出:[]示例3:输入:root=[1]输出:[1]代码实现:cla......
  • #yyds干货盘点# LeetCode 热题 HOT 100:不同的二叉搜索树
    题目:给你一个整数n,求恰由n个节点组成且节点值从1到n互不相同的二叉搜索树有多少种?返回满足题意的二叉搜索树的种数。 示例1:输入:n=3输出:5示例2:输入:n=1输出:1......
  • 关于ToString()、ToKnowColor()及属性返回值为类类型的理解
    1usingSystem;2usingSystem.Collections.Generic;3usingSystem.Linq;4usingSystem.Text;5usingSystem.Drawing;6usingSystem.Windows.Forms......
  • 输出cout
    输出cout输出格式cout<< ;inta=3;doubleb=1.5;cout<<a;cout<<b;连续输出inta=3;doubleb=1.5;cout<<a<<b;输出结果为31.5原因:cout先输出了a......
  • AcWing1251 打击罪犯--并查集
    #include<bits/stdc++.h>#definepbpush_back#definefifirst#definesesecond#defineall(x)(x).begin(),(x).end()#defineSZ(x)(int)(x).size()usingnam......
  • 基于kaldi的语音识别:chain模型的finetune通用步骤
    前记:先说下模型训练的背景。正如一般的机器学习的模型训练那样,首先会用较大的数据集训练生成一个较大的模型,然后在这个模型基础上进行调优,也就是finetune。 我这边基于k......
  • CSP-S模拟19
    A.木棍发现一共就\((334)(3322)(442)(4222)(22222)\)几种情况,发现\(2\)通配,最后考虑他即可,先考虑\(334\)然后$3/4$必然只剩一种可以贡献,分别算一下,最后处理剩......
  • wpf: StackPanel和WrapPanel
    他们是垂直面板和水平面板 StackPanel是默认垂直的,而且受到设定的宽度和高度影像,不管是Orientation为Horizontal还是vertical超过预设值的大小就会不显示,并不会换行 ......
  • iCells(Excel插件)第一个版本正式发布
    一、界面二、功能(一)支持连续撤销,由于时间有限,未能开发全部的撤销功能,后期将逐步加入。(二)多种号码校验,特别是身份证校验,对于身份证录入后的校验简直是神器。(三)随机功能......