C# 为什么在比较中使用IComparable比使用字符串慢?
我使用Mergesort对50.000.000个字符串进行排序,根据我使用的参数类型有两种不同的结果 使用接口IComparable: 20226毫秒 直接使用字符串: 10912毫秒 合并排序代码:C# 为什么在比较中使用IComparable比使用字符串慢?,c#,.net,string,performance,C#,.net,String,Performance,我使用Mergesort对50.000.000个字符串进行排序,根据我使用的参数类型有两种不同的结果 使用接口IComparable: 20226毫秒 直接使用字符串: 10912毫秒 合并排序代码: public class Mergesort2 { static private StringComparer comparer1 = StringComparer.Ordinal; public static void merge(IComparable[] a, ICompar
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,那么性能将会提高。为什么使用不同的类型会有如此巨大的性能差异?我必须简单地说:对象本身的大小。。。因此,每次你声明一个ICompare,它包含一堆额外的属性、方法等,比声明较小的字符串花费更多的时间…IMHO,我必须简单地说:对象本身的大小。。。因此,每次您声明一个包含大量额外属性、方法等的ICompare时,要比声明较小的字符串花费更多的时间…来回答有关性能的问题:您的测试使用的字符串足够小,使用非泛型IComparable接口需要额外的类型检查,随着使用接口分派而不是虚拟分派,像.NET和Java VM这样的虚拟机的低级细节比字符串比较更昂贵。如果使用带有长公共前缀的字符串,比较操作将成为主要的性能成本,两种形式之间的差距将缩小。编辑:在代码的发布版本上运行测试可能会缩小差距,因为没有在本地运行测试,我不确定OP用于测试的版本是什么 现在对于整个实验来说更重要的是,忽略代码的所有其他问题,我将特别指出在.NET中以通用方式支持类似项的常见做法 不要限制泛型类型T可能是字符串,也可能不是字符串,如果它可能实现也可能不实现IComparable的话。 使用IComparer来比较元素。如果用户将comparer参数的null传递给其中一个公共方法,则默认为comparer.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);
}
}
为了回答关于性能的问题:您的测试使用了足够小的字符串,使用非泛型IComparable接口需要额外的类型检查,随着使用接口分派而不是虚拟分派,像.NET和Java VM这样的虚拟机的低级细节比字符串比较更昂贵。如果使用带有长公共前缀的字符串,比较操作将成为主要的性能成本,两种形式之间的差距将缩小。编辑:在代码的发布版本上运行测试可能会缩小差距,因为没有在本地运行测试,我不确定OP用于测试的版本是什么 现在对于整个实验来说更重要的是,忽略代码的所有其他问题,我将特别指出在.NET中以通用方式支持类似项的常见做法 不要限制泛型类型T可能是字符串,也可能不是字符串,如果它可能实现也可能不实现IComparable的话。 使用IComparer来比较元素。如果用户将comparer参数的null传递给其中一个公共方法,则默认为comparer.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);
}
}
如果你把它改为通用的,使用IComparable呢?是的,这是一个选项,但我在使用字符串方面没有什么问题,我只是想了解其中的区别。至少100000000次转换是昂贵的。例如,在返回比较器1.Comparev中,w<0StringComparer.Compare对对象和字符串具有不同的重载。字符串版本最有可能是高度优化的,而对象版本只提供常规的.NET性能,并且鉴于其一般性质,编译器更难优化。对象比较StringComparer到底有什么用?在我看来,它只是在履行提供对象接口的合同义务。为什么要将StringComparer与字符串以外的任何东西一起使用,对我来说似乎是个谜。我支持@TimSchmelter。无意义的强制转换。如果你让它成为通用的,而使用IComparable呢?是的,这是一个选项,但我在使用字符串方面并没有问题,我只是想了解区别。至少100000000次强制转换是昂贵的。例如,在返回比较器1.Comparev中,w<0StringComparer.Compare对对象和字符串具有不同的重载。字符串版本最有可能是高度优化的,而对象版本只提供常规的.NET性能,并且鉴于其一般性质,编译器更难优化。对象比较StringComparer到底有什么用?在我看来,它只是在履行提供对象接口的合同义务。为什么要将StringComparer与字符串以外的任何东西一起使用,对我来说似乎是个谜。我支持@TimSchmelter。无意义cas
这完全不对。另外,再看一下字符串。它有很多方法,但正如我所说的,这在这里根本不相关。你能给我们指一些支持你答案的文档吗?它在哪里说实例化具有更多属性和/或方法的对象需要更长的时间?你真的是说将字符串强制转换为IComparable会分配一个新对象吗?那不对。12个对象,一个有1000 000个属性,另一个有5个。在单独的网页上,在Page_Load事件上创建每个对象的单个实例。在网页中,放置一条消息。当然,创建具有更多属性的对象需要更长的时间!如果你愿意的话,自己量一量!一个或几个毫秒以上。。。超过50000次,等于秒。2汇编程序级转换:1-查看对象是否兼容,2-创建新对象,3-在新中放置旧属性,4-在mem地址X销毁旧,5-在同一mem地址放置新。。。这是我的知识无论如何。。。在这里,还有几毫秒!那完全不对。另外,再看一下字符串。它有很多方法,但正如我所说的,这在这里根本不相关。你能给我们指一些支持你答案的文档吗?它在哪里说实例化具有更多属性和/或方法的对象需要更长的时间?你真的是说将字符串强制转换为IComparable会分配一个新对象吗?那不对。12个对象,一个有1000 000个属性,另一个有5个。在单独的网页上,在Page_Load事件上创建每个对象的单个实例。在网页中,放置一条消息。当然,创建具有更多属性的对象需要更长的时间!如果你愿意的话,自己量一量!一个或几个毫秒以上。。。超过50000次,等于秒。2汇编程序级转换:1-查看对象是否兼容,2-创建新对象,3-在新中放置旧属性,4-在mem地址X销毁旧,5-在同一mem地址放置新。。。这是我的知识无论如何。。。在这里,还有几毫秒!有趣,但我不明白三件事:1你怎么知道OP用于测试的字符串是什么类型的?2如果您能给我指出一些文献/规范,其中说虚拟分派比.NET中的接口分派快,我将不胜感激。3为什么默认为Comparer。默认为非IComparable类型?就我个人而言,我会将T限制为IComparable,因为如果该接口实现缺失,那么很可能该类型不是设计为可排序的,即排序操作可能对该类型没有任何意义。我将该程序构建为发行版。但我认为你的代码无法编译。我想你把我的同事和我搞混了IComparable@stakx在.NET中,不将类型标记为IComparable是完全有效的,而只依赖于提供IComparaer实现的开发人员。其中一个例子是SortedList类:@user2025998如果我的代码示例中出现IComparable,那么我犯了一个错误。我的意图是将IComparer用作比较T类型对象的唯一接口。@stakx:re 2,它比这更复杂。在.NET中,为接口分派和虚拟分派选择的策略比较复杂,并且随着时间的推移已经发生了变化。肯定有这样的场景,其中一个比另一个快,但我不想准确描述它们在我脑海中的位置。在Roslyn的早期,我们对此做了很多研究,但是我不记得结果了,我现在也没有能力去查找它们。Vance Morrison 2006年的文章是一个很好的开始,但可能已经过时了:很有趣,但我不明白三件事:1.你怎么知道OP用于测试的是哪种字符串?2如果您能给我指出一些文献/规范,其中说虚拟分派比.NET中的接口分派快,我将不胜感激。3为什么默认为Comparer。默认为非IComparable类型?就我个人而言,我会将T限制为IComparable,因为如果该接口实现缺失,那么很可能该类型不是设计为可排序的,即排序操作可能对该类型没有任何意义。我将该程序构建为发行版。但我认为你的代码无法编译。我想你把我的同事和我搞混了IComparable@stakx在.NET中,不将类型标记为IComparable是完全有效的,而只依赖于提供IComparaer实现的开发人员。其中一个例子是SortedList类:@user2025998如果我的代码示例中出现IComparable,那么我犯了一个错误。我的意图是将IComparer用作比较T类型对象的唯一接口。@stakx:re 2,它比这更复杂。在.NET中,为接口分派和虚拟分派选择的策略比较复杂,并且随着时间的推移已经发生了变化。肯定有这样的场景,其中一个比另一个快,但我不想准确描述它们在我脑海中的位置。我们在早期就做了大量的研究 是Roslyn的,但我记不起结果了,我现在没有能力去查。万斯·莫里森(Vance Morrison)2006年的文章是一个很好的开始,但可能已经过时了: