11

考虑:

int a = 42;

// Reference equality on two boxed ints with the same value
Console.WriteLine( (object)a == (object)a ); // False

// Same thing - listed only for clarity
Console.WriteLine(ReferenceEquals(a, a));  // False

显然,每条装箱指令都分配了一个单独的装箱实例Int32,这就是它们之间的引用相等性失败的原因。此页面似乎表明这是指定的行为:

box 指令将“原始”(未装箱)值类型转换为对象引用(O 类型)。这是通过创建一个新对象 并将数据从值类型复制到新分配的对象中来实现的。

但是为什么一定要这样呢?是否有任何令人信服的理由说明 CLR 不选择Int32为所有原始值类型(都是不可变的)保存装箱 s 的“缓存”,甚至更强大的通用值?我知道Java有这样的东西。

ArrayList在没有泛型的时代,对于主要由小整数组成的大数据,它不会对减少内存需求和 GC 工作量有很大帮助吗?我也确信存在几个确实使用泛型的现代.NET 应用程序,但无论出于何种原因(反射、接口分配等),运行大型装箱分配,可以通过(似乎是)大量减少简单的优化。

那么是什么原因呢?我没有考虑到一些性能影响(我怀疑测试该项目是否在缓存中等是否会导致净性能损失,但我知道什么)?实施困难?不安全代码的问题?打破向后兼容性(我想不出一个好的程序应该依赖现有行为的任何充分理由)?或者是其他东西?

编辑:我真正建议的是“常见”原语的静态缓存,就像 Java 所做的那样。有关示例实现,请参阅 Jon Skeet 的回答。我知道在运行时对任意、可能可变的值类型或动态“记忆”实例执行此操作是完全不同的事情。

编辑:为清楚起见更改了标题。

4

6 回答 6

11

发现令人信服的一个原因是一致性。正如您所说,Java确实在一定范围内缓存装箱值......这意味着编写可以运行一段时间的代码太容易了:

// Passes in all my tests. Shame it fails if they're > 127...
if (value1 == value2) {
    // Do something
}

我一直被这个咬伤 - 幸运的是,诚然是在测试而不是生产代码中,但是有一些东西在给定范围之外显着改变行为仍然是令人讨厌的。

不要忘记任何条件行为也会对所有装箱操作产生成本 - 所以在它不使用缓存的情况下,您实际上会发现它更慢(因为它首先必须检查是否使用缓存)。

如果你真的想编写自己的缓存框操作,当然可以这样做:

public static class Int32Extensions
{
    private static readonly object[] BoxedIntegers = CreateCache();

    private static object[] CreateCache()
    {
        object[] ret = new object[256];
        for (int i = -128; i < 128; i++)
        {
            ret[i + 128] = i;
        }
    }

    public object Box(this int i)
    {
        return (i >= -128 && i < 128) ? BoxedIntegers[i + 128] : (object) i;
    }
}

然后像这样使用它:

object y = 100.Box();
object z = 100.Box();

if (y == z)
{
    // Cache is working
}
于 2010-11-23T14:11:43.283 回答
3

我不能声称能够读心,但这里有几个因素:

1)缓存值类型会导致不可预测性- 比较两个相等的装箱值可能是真或假,具体取决于缓存命中和实现。哎哟!

2)装箱值类型的生命周期很可能很短 - 那么您将值保存在缓存中多长时间?现在您要么有很多不再使用的缓存值,要么您需要使 GC 实现更加复杂以跟踪缓存值类型的生命周期。

有了这些不利因素,潜在的胜利是什么?在执行大量等值类型的长期装箱的应用程序中,内存占用更小。由于这次胜利会影响少数应用程序并且可以通过更改代码来解决,因此我将同意 c# 规范编写者在这里的决定。

于 2010-11-23T14:20:56.507 回答
3

装箱的值对象不一定是不可变的。可以更改装箱值类型中的值,例如通过接口。

因此,如果装箱一个值类型总是基于相同的原始值返回相同的实例,它将创建可能不合适的引用(例如,碰巧具有相同值的两个不同的值类型实例最终具有相同的引用,即使虽然他们不应该)。

public interface IBoxed
{
    int X { get; set; }
    int Y { get; set; }
}

public struct BoxMe : IBoxed
{
    public int X { get; set; }

    public int Y { get; set; }
}

public static void Test()
{
    BoxMe original = new BoxMe()
                        {
                            X = 1,
                            Y = 2
                        };
    
    object boxed1 = (object) original;
    object boxed2 = (object) original;

    ((IBoxed) boxed1).X = 3;
    ((IBoxed) boxed1).Y = 4;

    Console.WriteLine("original.X = " + original.X);
    Console.WriteLine("original.Y = " + original.Y);
    Console.WriteLine("boxed1.X = " + ((IBoxed)boxed1).X);
    Console.WriteLine("boxed1.Y = " + ((IBoxed)boxed1).Y);
    Console.WriteLine("boxed2.X = " + ((IBoxed)boxed2).X);
    Console.WriteLine("boxed2.Y = " + ((IBoxed)boxed2).Y);
}

产生这个输出:

原始.X = 1

原始.Y = 2

盒装1.X = 3

盒装1.Y = 4

盒装2.X = 1

盒装2.Y = 2

如果装箱没有创建新实例,那么 boxed1 和 boxed2 将具有相同的值,如果它们是从不同的原始值类型实例创建的,这将是不合适的。

于 2010-11-23T14:22:41.893 回答
1

对此有一个简单的解释:取消/装箱速度很快。它需要回到 .NET 1.x 时代。在 JIT 编译器为它生成机器代码之后,只为它生成了少量的 CPU 指令,所有指令都是内联的,没有方法调用。不计算诸如可为空类型和大型结构之类的极端情况。

查找缓存值的工作会大大降低此代码的速度。

于 2010-11-27T10:20:13.033 回答
0

我认为运行时填充缓存不是一个好主意,但我认为在 64 位系统上将 64 个可能的对象引用值中的约 80 亿定义为整数或浮动文字,并在任何系统上预先装箱所有原始文字。测试引用类型的高 31 位是否包含某个值可能应该比内存引用便宜。

于 2011-01-27T16:05:26.200 回答
0

添加到已经列出的答案的事实是,在 .net 中,至少对于普通的垃圾收集器,对象引用在内部存储为直接指针。这意味着当执行垃圾回收时,系统必须更新对每个被移动对象的单个引用,但这也意味着“主线”操作可以非常快。如果对象引用有时是直接指针,有时是其他指针,则每次取消引用对象时都需要额外的代码。由于对象取消引用是 .net 程序执行期间最常见的操作之一,因此即使是 5% 的减速也将是毁灭性的,除非它与惊人的加速相匹配。例如,“64 位紧凑”模型是可能的,其中每个对象引用是对象表的 32 位索引,可能比每个引用都是 64 位直接指针的现有模型提供更好的性能。延迟操作将需要额外的表查找,这会很糟糕,但对象引用会更小,因此可以一次将更多的对象存储在缓存中。在某些情况下,这可能是一个重大的性能胜利(也许经常是值得的——也许不是)。但是,尚不清楚允许对象引用有时是直接内存指针,有时是其他东西是否真的会提供很多优势。但是对象引用会更小,因此可以一次将更多的引用存储在缓存中。在某些情况下,这可能是一个重大的性能胜利(也许经常是值得的——也许不是)。但是,尚不清楚允许对象引用有时是直接内存指针,有时是其他东西是否真的会提供很多优势。但是对象引用会更小,因此可以一次将更多的引用存储在缓存中。在某些情况下,这可能是一个重大的性能胜利(也许经常是值得的——也许不是)。但是,尚不清楚允许对象引用有时是直接内存指针,有时是其他东西是否真的会提供很多优势。

于 2012-02-27T00:31:18.973 回答