【问题标题】:Why is using IComparable slower than strings in comparisons?为什么在比较中使用 IComparable 比使用字符串慢?
【发布时间】:2013-03-10 01:27:11
【问题描述】:

我正在使用 Mergesort 订购 50.000.000 个字符串,根据我使用的参数类型有两种不同的结果。

使用 IComparable 接口:

  • 20226 毫秒

直接使用字符串:

  • 10912 毫秒

合并排序代码:

public class Mergesort2
{
    static private StringComparer comparer1 = StringComparer.Ordinal;
    public static void merge(IComparable[] a, IComparable[] aux, int lo, int mid, int hi)
    {

        for (int k = lo; k <= hi; k++)
        {
            aux[k] = a[k];
        }

        // merge back to a[]
        int i = lo, j = mid + 1;
        for (int k = lo; k <= hi; k++)
        {
            if (i > mid)
            {
                a[k] = aux[j++];
            }
            else if (j > hi)
            {
                a[k] = aux[i++];
            }
            else if (less(aux[j], aux[i]))
            {
                a[k] = aux[j++];
            }
            else
            {
                a[k] = aux[i++];
            }
        }

    }

    private static void sort(IComparable[] a, IComparable[] aux, int lo, int hi)
    {
        if (hi <= lo)
        {
            return;
        }
        int mid = lo + (hi - lo) / 2;
        sort(a, aux, lo, mid);
        sort(a, aux, mid + 1, hi);
        merge(a, aux, lo, mid, hi);
    }

    public static void sort(IComparable[] a)
    {
        IComparable[] aux = new IComparable[a.Length];
        sort(a, aux, 0, a.Length - 1);
    }


    ///*********************************************************************
    ///  Helper sorting functions
    /// **********************************************************************

    // is v < w ?
    private static bool less(IComparable v, IComparable w)
    {
        return (comparer1.Compare(v, w) < 0);
    }

    // exchange a[i] and a[j]
    private static void exch(Object[] a, int i, int j)
    {
        Object swap = a[i];
        a[i] = a[j];
        a[j] = swap;
    }

    /// <summary>
    ///*********************************************************************
    ///  Index mergesort
    /// **********************************************************************
    /// </summary>
    // stably merge a[lo .. mid] with a[mid+1 .. hi] using aux[lo .. hi]
    private static void merge(IComparable[] a, int[] index, int[] aux, int lo, int mid, int hi)
    {

        // copy to aux[]
        for (int k = lo; k <= hi; k++)
        {
            aux[k] = index[k];
        }

        // merge back to a[]
        int i = lo, j = mid + 1;
        for (int k = lo; k <= hi; k++)
        {
            if (i > mid)
            {
                index[k] = aux[j++];
            }
            else if (j > hi)
            {
                index[k] = aux[i++];
            }
            else if (less(a[aux[j]], a[aux[i]]))
            {
                index[k] = aux[j++];
            }
            else
            {
                index[k] = aux[i++];
            }
        }
    }

    // return a permutation that gives the elements in a[] in ascending order
    // do not change the original array a[]
    public static int[] indexSort(IComparable[] a)
    {
        int N = a.Length;
        int[] index = new int[N];
        for (int i = 0; i < N; i++)
        {
            index[i] = i;
        }

        int[] aux = new int[N];
        sort(a, index, aux, 0, N - 1);
        return index;
    }

    // mergesort a[lo..hi] using auxiliary array aux[lo..hi]
    private static void sort(IComparable[] a, int[] index, int[] aux, int lo, int hi)
    {
        if (hi <= lo)
        {
            return;
        }
        int mid = lo + (hi - lo) / 2;
        sort(a, index, aux, lo, mid);
        sort(a, index, aux, mid + 1, hi);
        merge(a, index, aux, lo, mid, hi);
    }
}

此代码会产生缓慢的运行时。

如果我将所有 IComparable 类型更改为 String,性能将会提高。为什么使用不同的类型会有如此巨大的性能差异?

【问题讨论】:

  • 如果你把它变成通用的并使用IComparable&lt;T&gt; 来代替呢?
  • 是的,这是一个选项,但我对使用字符串并没有什么问题,我只是想了解其中的区别。
  • 至少 100000000 次演员表是昂贵的。例如在return (comparer1.Compare(v, w) &lt; 0)
  • StringComparer.Compare 对对象和字符串有不同的重载。字符串版本很可能是高度优化的,而对象版本只是为您提供“常规 .NET 性能”(鉴于其一般性质,编译器更难优化)。
  • object 比较 StringComparer 有什么用?在我看来,它只是履行提供对象接口的合同义务。为什么你要使用StringComparer 和字符串以外的任何东西对我来说似乎是个谜。我和@TimSchmelter 在这方面。毫无意义的演员表。

标签: c# .net string performance


【解决方案1】:

要回答有关性能的问题:您的测试使用的字符串足够小,以至于使用非泛型 IComparable 接口所需的额外类型检查,以及使用 interface-dispatch 而不是 virtual-dispatch(a .NET 和 Java VM 等虚拟机的低级细节)比字符串比较更昂贵。如果您使用具有长公共前缀的字符串,比较操作将成为主要的性能成本,并且两种形式之间的差距将缩小。 编辑:在代码的 Release 版本上运行测试也可能缩小差距(没有在本地运行测试,我不确定 OP 用于测试的版本是什么)。

现在对于整个实验来说更重要的是,忽略代码的所有其他问题,我将特别指出支持“可比较”项目的常见做法在 .NET 中以通用方式。

  1. 不要限制泛型类型T(可能是也可能不是string,事实上它可能实现也可能不实现IComparable)。
  2. 使用IComparer&lt;T&gt; 比较元素。如果用户将 nullcomparer 参数传递给公共方法之一,则默认为 Comparer&lt;T&gt;.Default

这是更新后的代码:

public class Mergesort2
{
    public static void merge<T>(T[] a, T[] aux, int lo, int mid, int hi, IComparer<T> comparer)
    {
        comparer = comparer ?? Comparer<T>.Default;

        for (int k = lo; k <= hi; k++)
        {
            aux[k] = a[k];
        }

        // merge back to a[]
        int i = lo, j = mid + 1;
        for (int k = lo; k <= hi; k++)
        {
            if (i > mid)
            {
                a[k] = aux[j++];
            }
            else if (j > hi)
            {
                a[k] = aux[i++];
            }
            else if (less(aux[j], aux[i], comparer))
            {
                a[k] = aux[j++];
            }
            else
            {
                a[k] = aux[i++];
            }
        }

    }

    private static void sort<T>(T[] a, T[] aux, int lo, int hi, IComparer<T> comparer)
    {
        if (hi <= lo)
        {
            return;
        }
        int mid = lo + (hi - lo) / 2;
        sort(a, aux, lo, mid, comparer);
        sort(a, aux, mid + 1, hi, comparer);
        merge(a, aux, lo, mid, hi, comparer);
    }

    public static void sort<T>(T[] a, IComparer<T> comparer)
    {
        comparer = comparer ?? Comparer<T>.Default;
        T[] aux = new T[a.Length];
        sort(a, aux, 0, a.Length - 1, comparer);
    }


    ///*********************************************************************
    ///  Helper sorting functions
    /// **********************************************************************

    // is v < w ?
    private static bool less<T>(T v, T w, IComparer<T> comparer)
    {
        return (comparer.Compare(v, w) < 0);
    }

    // exchange a[i] and a[j]
    private static void exch<T>(T[] a, int i, int j)
    {
        T swap = a[i];
        a[i] = a[j];
        a[j] = swap;
    }

    /// <summary>
    ///*********************************************************************
    ///  Index mergesort
    /// **********************************************************************
    /// </summary>
    // stably merge a[lo .. mid] with a[mid+1 .. hi] using aux[lo .. hi]
    private static void merge<T>(T[] a, int[] index, int[] aux, int lo, int mid, int hi, IComparer<T> comparer)
    {

        // copy to aux[]
        for (int k = lo; k <= hi; k++)
        {
            aux[k] = index[k];
        }

        // merge back to a[]
        int i = lo, j = mid + 1;
        for (int k = lo; k <= hi; k++)
        {
            if (i > mid)
            {
                index[k] = aux[j++];
            }
            else if (j > hi)
            {
                index[k] = aux[i++];
            }
            else if (less(a[aux[j]], a[aux[i]], comparer))
            {
                index[k] = aux[j++];
            }
            else
            {
                index[k] = aux[i++];
            }
        }
    }

    // return a permutation that gives the elements in a[] in ascending order
    // do not change the original array a[]
    public static int[] indexSort<T>(T[] a, IComparer<T> comparer)
    {
        comparer = comparer ?? Comparer<T>.Default;
        int N = a.Length;
        int[] index = new int[N];
        for (int i = 0; i < N; i++)
        {
            index[i] = i;
        }

        int[] aux = new int[N];
        sort(a, index, aux, 0, N - 1, comparer);
        return index;
    }

    // mergesort a[lo..hi] using auxiliary array aux[lo..hi]
    private static void sort<T>(T[] a, int[] index, int[] aux, int lo, int hi, IComparer<T> comparer)
    {
        if (hi <= lo)
        {
            return;
        }
        int mid = lo + (hi - lo) / 2;
        sort(a, index, aux, lo, mid, comparer);
        sort(a, index, aux, mid + 1, hi, comparer);
        merge(a, index, aux, lo, mid, hi, comparer);
    }
}

【讨论】:

  • 很有意思,但我不明白三件事:(1) 你怎么知道 OP 用于测试的字符串是什么类型的? (2) 如果您能指出一些文献/规范说虚拟调度比 .NET 中的“接口调度”更快,我将不胜感激。 (3) 为什么非IComparable 类型默认使用Comparer&lt;T&gt;.Default?我个人T限制为IComparable,因为如果缺少该接口实现,那么很可能该类型不是可排序的(即排序操作可能根本没有意义对于那种类型)。
  • 我正在将程序构建为 Release。但我认为您的代码无法编译。我认为您混淆了 IComparer 和 IComparable
  • @stakx 在 .NET 中不将类型标记为IComparable&lt;T&gt; 是完全有效的,而是仅依赖于提供IComparer&lt;T&gt; 实现的开发人员。这方面的众多示例之一是 SortedList 类:msdn.microsoft.com/en-us/library/ms132319.aspx
  • @user2025998 如果IComparable 出现在我的代码示例中,那么我犯了一个错误。我的意图是使用IComparer&lt;T&gt; 作为比较T 类型对象的唯一接口。
  • @stakx:关于 #2,它比这更复杂。在 .NET 中为接口调度与虚拟调度选择的策略很棘手,并且随着时间的推移发生了微妙的变化。肯定有一种情况比另一种更快,但我不想准确地描述它们在我脑海中的含义。在 Roslyn 的早期,我们对此进行了很多研究,但我不记得结果了,我现在也没有能力查找它们。 Vance Morrison 2006 年的文章是一个不错的起点,但可能已经过时:blogs.msdn.com/b/vancem/archive/2006/03/13/550529.aspx
【解决方案2】:

恕我直言,我不得不简单地说:对象本身的大小......所以每次你声明一个 ICompare 时,它​​包含一堆额外的属性、方法等,它需要更多的时间,而不是你声明较小的字符串...

【讨论】:

  • 这根本不正确。另外,再看看String。它有很多方法,但正如我所说,这与这里无关。
  • 您能否向我们指出一些支持您的答案的文档?它在哪里说实例化具有更多属性和/或方法的对象需要更长的时间?
  • 您真的是说将string 转换为IComparable 会分配一个新对象吗?这是不对的。
  • 1) 2 个对象,1 个具有 1 000 000 个属性,其他具有 5 个。在单独的网页上,在 Page_Load 事件中为每个对象创建单个实例。在网页中,放置一条消息。当然,创建具有更多属性的对象需要更长的时间!如果您愿意,请自行衡量!多一个或几毫秒……超过 50 000 000 次,等于秒。 2) 汇编器级别转换:1-查看对象是否兼容,2-创建新对象,3-将旧的属性放在新的,4-在内存地址 X 处销毁旧的,5-将新的在同一内存地址...无论如何,我的知识......再次在这里,多几毫秒!
猜你喜欢
  • 1970-01-01
  • 2011-06-21
  • 2012-02-09
  • 2013-01-02
  • 2011-07-09
  • 2020-05-24
  • 1970-01-01
  • 1970-01-01
  • 2017-03-28
相关资源
最近更新 更多