为何直接传参的string.Format比显式传object[]内存占用高百倍?
测试用例与结果
核心测试代码:
// Test Case 2 for (int i = 0; i < 10000000; i++) { var message = string.Format("Test Message{0}{1}{2}{3}{4}", int1, int2, int3, int4, int5); } // Test Case 3 for (int i = 0; i < 10000000; i++) { var message = string.Format("Test Message{0}{1}{2}{3}{4}", new object[] { int1, int2, int3, int4, int5 }); }
测试结果:
Test Case 2 - Elapsed Time: 1770 ms Test Case 2 - Memory Used: 28528 bytes Test Case 3 - Elapsed Time: 1685 ms Test Case 3 - Memory Used: 360 bytes
差异原因
造成内存占用差距的核心原因是JIT编译器的循环不变量优化,结合.NET的装箱缓存机制共同作用:
显式数组的循环不变量提升
Test Case3中,显式创建的new object[] { int1, int2, int3, int4, int5 }里,所有int参数都是循环外定义的固定值(1-5),JIT编译器能识别到这个数组的内容在整个循环过程中不会变化,因此会自动将数组的创建和初始化逻辑提升到循环外面——也就是说整个1000万次循环只创建了1个object数组,而不是每次循环都创建新数组。params重载的隐式数组无法被优化
Test Case2使用的是string.Format的params object[]重载,编译器会自动生成代码,在每次调用方法时隐式创建一个object数组来包装参数。JIT编译器通常不会对这种由编译器隐式生成的数组进行循环不变量提升,因此每次循环都会创建一个新的object[5]数组,累计下来分配了大量数组对象。虽然GC会回收大部分,但仍有部分对象在测试结束时未被完全回收,导致内存占用统计值更高。装箱缓存的影响
你的测试参数int1-int5都是1-5,属于.NET为int类型预设的装箱缓存范围(-128到127),这些值装箱时会重用缓存中的对象,不会每次创建新的object实例,因此装箱操作本身不会带来明显的内存开销,这也是两者内存差异主要来自数组的原因。
补充验证
如果修改Test Case3的参数,让int值在循环中动态变化(比如int1 = i),JIT就无法再进行数组提升优化,此时两者的内存占用会变得相近——因为每次循环都必须创建新的数组和装箱对象。
内容的提问来源于stack exchange,提问作者Oleksiy Savytskyy

