C#对象比较和内存分配

时间:2014-03-14 08:53:25

标签: c#

对于大多数原始类型,我的同事有10个CopyIfDifferent函数。 stringintdoublebool ...

我做了1000万件物品的测试,在我的机器上更改值比检查它们是否不同然后更改它们快了大约40%。我测试了1000万件不需要更换的物品和1000万件需要更换的物品。

当问他为什么有这些功能而不是仅仅因为它更便宜而改变价值。他告诉我,这是为了防止内存分配和内存碎片。因此,如果对象相同,它可以保留在相同的内存地址中,因此速度更快。

public static void CopyIfDifferent(ref string vValueToCopyTo, string vValueToCopyFrom)
{
    if (!ValuesAreEqual(vValueToCopyTo, vValueToCopyFrom))
    {
        vValueToCopyTo = vValueToCopyFrom;
    }
}
public static bool ValuesAreEqual(string vValue1, string vValue2)
{
    if (vValue1 == null && vValue2 == null)
    {
        return true;
    }
    if (vValue1 == null || vValue2 == null)
    {
        return false;
    }
    return vValue1 == vValue2;
}

使用功能

Utils.CopyIfDifferent(ref GroupIDFK, item.GroupIDFK);

我的问题是。在更改之前检查值而不仅仅是更改是否更好?如果是这样的话?

5 个答案:

答案 0 :(得分:4)

只看字符串函数,如果有什么事情会增加内存使用量,而不是减少它。 (但是,由于字符串实习,它通常不会实际上增加内存使用量,但它也无济于事。)

为什么呢?那么,让我们来看看方法:

public static void CopyIfDifferent(ref string vValueToCopyTo, string vValueToCopyFrom)
{
    if (!ValuesAreEqual(vValueToCopyTo, vValueToCopyFrom))
    {
        vValueToCopyTo = vValueToCopyFrom;
    }
}

现在考虑在字符串不同时调用它:

string s1 = "One";
string s2 = "Two";

CopyIfDifferent(ref s2, s1);

这会怎么做?好吧,它会做的完全相同:

s2 = s1;

只有方法调用的开销和昂贵的字符串比较。所以这毫无意义。

现在如果字符串相同怎么样?在这种情况下,它将不执行任何操作,并将s2保留为原始引用。这意味着我们现在有两个对具有相同内容而不是一个内容的字符串的引用 - 这表面上是浪费内存并使事情变得更糟。幸运的是,由于字符串实习,只有一个字符串的实际副本将(通常)保存在内存中,这可以稍微缓解这个问题。

总的来说,这完全是浪费时间,它会让事情变慢,你肯定不会这样做。

使用int等值类型执行此操作的方法是什么?如果实现如下:

public static void CopyIfDifferent(ref int vValueToCopyTo, int vValueToCopyFrom)
{
    if (!ValuesAreEqual(vValueToCopyTo, vValueToCopyFrom))
    {
        vValueToCopyTo = vValueToCopyFrom;
    }
}

public static bool ValuesAreEqual(int vValue1, int vValue2)
{
    return vValue1 == vValue2;
}

这会很糟糕......

一个简单的int x = y;变成了一个非常低效的指针推送到堆栈(ref param),然后取消引用指针(比较值)然后分配给指针价值观是不同的。

就像@dcastro在下面指出的那样,int正在被复制3次 - 这完全嘲弄了首先尝试优化int的复制。

只是......不。一千次,没有。

答案 1 :(得分:3)

关于防止内存分配和内存碎片,它对值类型没用,因为引用没有变化变量有自己的内存,其内容被替换,因此没有新的内存分配。

恕我直言,在设置值类型之前检查相等性的唯一原因是:

  • 你需要举办一些活动(比如OnChange)
  • 设置该值将启动复杂操作,例如将其保存在数据库中或刷新缓存
  • 实际上经常设置值类型。

一个简单的测试可以显示如何执行:

using System;
using System.Diagnostics;

namespace ConsoleApplication1
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Process proc = Process.GetCurrentProcess();
            Console.WriteLine("GetTotalMemory:{0} PrivateMemorySize64:{1}  press enter to continue", GC.GetTotalMemory(true), proc.PrivateMemorySize64);
            Console.ReadLine();

            const int testCount = 1000000000;
            var stopWatch = new Stopwatch();

            int a = 2456;
            int b = 2456;
            stopWatch.Start();
            for (int i = 0; i < testCount; i++)
            {
                CopyIfDifferent(ref a, b);
            }
            stopWatch.Stop();
            Console.WriteLine("int equal CopyIfDifferent ElapsedMilliseconds:{0}", stopWatch.ElapsedMilliseconds);
            Console.WriteLine("GetTotalMemory:{0} PrivateMemorySize64:{1}  press enter to continue", GC.GetTotalMemory(true), proc.PrivateMemorySize64);
            GC.Collect();
            Console.ReadLine();
            stopWatch.Restart();
            for (int i = 0; i < testCount; i++)
            {
                a = b;
            }
            stopWatch.Stop();
            Console.WriteLine("int equal assignment ElapsedMilliseconds:{0}", stopWatch.ElapsedMilliseconds);
            Console.WriteLine("GetTotalMemory:{0} PrivateMemorySize64:{1}  press enter to continue", GC.GetTotalMemory(true), proc.PrivateMemorySize64);
            Console.ReadLine();
            string c = "dfsgdgdfg";
            string d = "dfsgdgdfg";
            stopWatch.Restart();
            for (int i = 0; i < testCount; i++)
            {
                CopyIfDifferent(ref c, d);
            }
            stopWatch.Stop();
            Console.WriteLine("string equal CopyIfDifferent ElapsedMilliseconds:{0}", stopWatch.ElapsedMilliseconds);
            Console.WriteLine("GetTotalMemory:{0} PrivateMemorySize64:{1}  press enter to continue", GC.GetTotalMemory(true), proc.PrivateMemorySize64);
            Console.ReadLine();
            stopWatch.Restart();
            for (int i = 0; i < testCount; i++)
            {
                c = d;
            }
            stopWatch.Stop();
            Console.WriteLine("string equal assignment ElapsedMilliseconds:{0}", stopWatch.ElapsedMilliseconds);
            Console.WriteLine("GetTotalMemory:{0} PrivateMemorySize64:{1}  press enter to continue", GC.GetTotalMemory(true), proc.PrivateMemorySize64);
            Console.ReadLine();
            int e = 2456;
            int f = 3465464;
            stopWatch.Restart();
            for (int i = 0; i < testCount; i++)
            {
                CopyIfDifferent(ref e, f);
            }
            stopWatch.Stop();
            Console.WriteLine("int different CopyIfDifferent ElapsedMilliseconds:{0}", stopWatch.ElapsedMilliseconds);
            Console.WriteLine("GetTotalMemory:{0} PrivateMemorySize64:{1}  press enter to continue", GC.GetTotalMemory(true), proc.PrivateMemorySize64);
            Console.ReadLine();
            stopWatch.Restart();
            for (int i = 0; i < testCount; i++)
            {
                e = f;
            }
            stopWatch.Stop();
            Console.WriteLine("int different assignment ElapsedMilliseconds:{0}", stopWatch.ElapsedMilliseconds);
            Console.WriteLine("GetTotalMemory:{0} PrivateMemorySize64:{1}  press enter to continue", GC.GetTotalMemory(true), proc.PrivateMemorySize64);
            Console.ReadLine();
            string g = "dfsgdgdfg";
            string h = "gdfhfghfghfghf";
            stopWatch.Restart();
            for (int i = 0; i < testCount; i++)
            {
                CopyIfDifferent(ref g, h);
            }
            stopWatch.Stop();
            Console.WriteLine("string different CopyIfDifferent ElapsedMilliseconds:{0}", stopWatch.ElapsedMilliseconds);
            Console.WriteLine("GetTotalMemory:{0} PrivateMemorySize64:{1}  press enter to continue", GC.GetTotalMemory(true), proc.PrivateMemorySize64);
            Console.ReadLine();
            stopWatch.Restart();
            for (int i = 0; i < testCount; i++)
            {
                g = h;
            }
            stopWatch.Stop();
            Console.WriteLine("string different assignment ElapsedMilliseconds:{0}", stopWatch.ElapsedMilliseconds);
            Console.WriteLine("GetTotalMemory:{0} PrivateMemorySize64:{1}  press enter to continue", GC.GetTotalMemory(true), proc.PrivateMemorySize64);
            Console.ReadLine();
        }
        public static void CopyIfDifferent(ref string vValueToCopyTo, string vValueToCopyFrom)
        {
            if (!ValuesAreEqual(vValueToCopyTo, vValueToCopyFrom))
            {
                vValueToCopyTo = vValueToCopyFrom;
            }
        }
        public static void CopyIfDifferent(ref int vValueToCopyTo, int vValueToCopyFrom)
        {
            if (vValueToCopyTo != vValueToCopyFrom)
            {
                vValueToCopyTo = vValueToCopyFrom;
            }
        }
        public static bool ValuesAreEqual(string vValue1, string vValue2)
        {
            if (vValue1 == null && vValue2 == null)
            {
                return true;
            }
            if (vValue1 == null || vValue2 == null)
            {
                return false;
            }
            return vValue1 == vValue2;
        }
    }
}

结果:

  

GetTotalMemory:22764 PrivateMemorySize64:6815744

     

int equal CopyIfDifferent ElapsedMilliseconds:1078   GetTotalMemory:36608 PrivateMemorySize64:6815744

     

int equal assignment ElapsedMilliseconds:540 GetTotalMemory:36608   PrivateMemorySize64:6815744

     

string equal CopyIfDifferent ElapsedMilliseconds:10782   GetTotalMemory:36608 PrivateMemorySize64:6815744

     

string equal assignment ElapsedMilliseconds:1077 GetTotalMemory:36608   PrivateMemorySize64:6815744

     

int不同的CopyIfDifferent ElapsedMilliseconds:1078   GetTotalMemory:36608 PrivateMemorySize64:6815744

     

int不同的赋值ElapsedMilliseconds:540 GetTotalMemory:36608   PrivateMemorySize64:6815744

     

字符串不同CopyIfDifferent ElapsedMilliseconds:10241   GetTotalMemory:36608 PrivateMemorySize64:6815744

     

字符串不同的赋值ElapsedMilliseconds:1076   GetTotalMemory:36608 PrivateMemorySize64:6815744

Resharper也告诉你它对值类型

没用

enter image description here

答案 2 :(得分:2)

无用,因为当你复制字符串时,它的引用更改(最多4或8个字节)。 重新发明轮子

String

至于 struct ,例如// Compare strings: case sensitive, current culture if (String.Equals(vValueToCopyTo, vValueToCopyFrom)) vValueToCopyTo = vValueToCopyFrom; // Compare strings: case insensitive and invariant culture if (String.Equals(vValueToCopyTo, vValueToCopyFrom, StringComparison.OrdinalIgnoreCase)) vValueToCopyTo = vValueToCopyFrom; ,它甚至有害:标准比较

int

将被优化程序淘汰(如果 if (a == b) a = b; 根本不需要分配a == b),那么它会花费你< EM>没有;当一点点不同的比较正在编译

a = b;

它很有可能只被翻译成 3汇编命令 if (a != b) // != instead of == a = b; );相反,特殊函数调用表示参数复制,附加XOR, JZ, MOVSUB汇编程序命令,反射信息等。

答案 3 :(得分:0)

是的,我猜它最有效的是交换指针(只是更改)而不是检查差异,因为检查会花费更多时间。

即使在检查参考相等性的最快检查的情况下,参考交换也会花费(或多或少)相同的时间。

答案 4 :(得分:0)

这是另一个角度:

对于字符串,可能存在这样做的情况。 (对于其他人来说没有意义)

在操作时,我们已经在堆中分配了两个字符串(我在谈论字符串本身而不是引用)并且在操作之后,很可能,其中一个字符串将不具有指向它的活动对象,将被垃圾收集。因此,如果堆上的两个字符串相同,那么保留先前分配的字符串会减少GC的工作量。

这是否值得检查每个字符串?可能不适用于大多数情况,但可能存在基于字符串数量,非实际更新频率等的极端情况。