出于好奇,我试图测试 List<T>
的性能同时使用 value
和 reference
类型。
结果与我预期的不同,这让我相信我对这些对象在内存中的布局方式的理解可能不正确。
这是我的实验:
创建一个基本的
class
仅包含两个成员,一个int
和一个bool
创建 2
List<T>
用于保存我的测试类(List1
和List2
)的对象随机生成测试对象并添加到
List1
和List2
交替Time 遍历
List1
需要多长时间(做一些任意的工作,例如递增计数器然后访问元素)
然后我用 struct
重复了一遍代替 class
我的假设是,当使用 class
时,在 List<T>
中的引用文献将是连续的,但由于我创建它们的方式(在添加到 List1
和 List2
之间切换),它们指向的对象可能不是。
我认为在使用 struct
时,因为它是一个值类型,对象本身将连续保存在内存中(因为 List<T>
保存实际项目而不是引用集合)。
因此,我期望 struct
表现更好(由于预取器等)
事实上,两者非常相似。
这是怎么回事?
编辑 - 添加代码以实际访问迭代器中的元素,包括代码示例
测试类(或结构)
public class/struct TestClass
{
public int TestInt;
public bool TestBool;
}
创建随机列表:
var list1 = new List<TestClass>();
var list2 = new List<TestClass>();
var toggle = false;
for (var i=0; i < 4000000; i++)
{
// Random object generation removed for simplicity
if (toggle)
list1.Add(randomObject);
else
list2.Add(randomObject);
toggle = !toggle;
}
测试:
var stopWatch = new Stopwatch();
var counter = 0;
var testBool = false;
stopwatch.Start();
foreach(var item in list1)
{
// Access the element
testBool = item.TestBool;
counter++;
}
stopwatch.Stop();
用 TestObject
重复作为class
和一个 struct
.
我意识到差别不大,但我预计 struct
表现明显优于class
最佳答案
// Access the element
testBool = item.TestBool;
那没有效果,优化器将删除该语句,因为它没有有用的副作用。您实际上并没有衡量结构和类之间的区别,因为您实际上从未访问过元素。
counter++;
同样的故事,很可能会被优化掉。除非您实际使用 计数器值,否则在循环完成后。让优化器删除太多代码并使测试变得毫无意义是一种常见的微基准测试风险。解决方法是:
foreach(var item in list1)
{
// Access the element
counter += item.TestInt;
}
Console.WriteLine(counter);
基准准则是:
- 仅由发布配置生成的配置文件代码。 Debug 构建会产生过多的额外代码并抑制优化
- 工具 + 选项、调试、常规,取消勾选“在模块加载时抑制 JIT 优化”。这确保即使您使用调试器运行也能获得优化的代码
- Debug + Windows + Disassembly 是一个非常重要的调试器窗口,可以向您显示真正 运行的代码。需要对机器代码有一定的了解才能正确解释该窗口
- 在测试代码周围放置一个外部循环以确保您至少运行测试 10 次非常重要。这消除了冷启动效应,例如处理器必须填充 L1 指令缓存以及必须从程序集中加载 IL 并在其第一次执行时编译它的抖动。并删除随机异常值,因为您必须与机器上运行的其他进程竞争并竞争处理器。
- 15% 的差异在统计学上不显着。
关于c# - List<struct> 与 List<class> 的性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23032494/