【问题标题】:Why ToUpper is faster than ToLower?为什么 ToUpper 比 ToLower 快?
【发布时间】:2017-05-05 10:11:05
【问题描述】:
Stopwatch stopwatch1 = new Stopwatch();
Stopwatch stopwatch2 = new Stopwatch();
string l = "my test";
string u = "MY TEST";

for (int i = 0; i < 25; i++)
{
    l += l;
    u += u;
}

stopwatch1.Start();
l=l.ToUpper();
stopwatch1.Stop();

stopwatch2.Start();
u=u.ToLower();
stopwatch2.Stop();

// Write result.
Console.WriteLine("Time elapsed: \nUPPER :  {0}\n LOWER : {1}",
                  stopwatch1.Elapsed, stopwatch2.Elapsed);

我跑了很多次:

UPPER : 00:00:01.3386287
LOWER : 00:00:01.4546552

UPPER : 00:00:01.1614189
LOWER : 00:00:01.1970368

UPPER : 00:00:01.2697430
LOWER : 00:00:01.3460950

UPPER : 00:00:01.2256813
LOWER : 00:00:01.3075738

【问题讨论】:

  • 首先,您应该为多次转换计时!试试 10000
  • 并尝试颠倒顺序以消除缓存影响
  • 您应该使用 ToUpperInvariant 和 ToLowerInvariant。并且您使用 BenchmarkDotNet 进行测试。
  • This might be the answer you are looking for。 “在规范化字符串时,强烈建议您使用 ToUpperInvariant 而不是 ToLowerInvariant,因为 Microsoft 已经优化了执行大写比较的代码。”

标签: c# .net


【解决方案1】:

让我们尝试再现结果

  // Please, notice: the same string for both ToUpper/ToLower
  string GiniPig = string.Concat(Enumerable
    .Range(1, 1000000) // a million chunks "my test/MyTest" combined (long string)
    .Select(item => "my test/MY TEST"));

   Stopwatch sw = new Stopwatch();

   // Let's try n (100) times - not just once
   int n = 100;

   var sampling = Enumerable
     .Range(1, n)
     .Select(x => {
        sw.Reset();
        sw.Start();

        GiniPig.ToLower(); // change this into .ToUpper();

        sw.Stop();
        return sw.ElapsedMilliseconds; })
     .ToSampling(x => x); // Side library; by you may save the data and analyze it with R

   Console.Write(
     $"N = {n}; mean = {sampling.Mean:F0}; std err = {sampling.StandardDeviation:F0}");

运行了几次(升温)我得到了结果(Core i7 3.6 GHz,.Net 4.6 IA-64):

ToLower: N = 100; mean = 38; std err = 8
ToUpper: N = 100; mean = 37; std err = 9

所以你不能拒绝ToLowerToUpper 一样快的零假设,因此你的实验有错误

  1. 您有不同的字符串要处理
  2. 处理(仅限175 字符)字符串一次(不在循环中)应该是即时的,因此错误可能很大
  3. 您必须预热例程(以便编译方法、加载程序集、填充缓存等)

看来(对于一个非常简单的操作来说,经过的时间超过 1 秒)是规则 #3(预热)破坏了实验

【讨论】:

  • 人们评论说“微软已经优化了执行大写比较的代码。”是不是因为大写字母的 ASCII 码只有 65 - 90 两个数字,而小写字母 97 -122 的 ASCII 码包含 3 个数字(需要更多处理)?
  • @Medo Medo:65122 都是以太 single charsingle byte;这就是时间(在您的示例中)相同的原因(CPU 不使用 digits,而是使用 bytes ints longs 等)
  • 当您重现结果时,您确定您与 OP 处于同一文化中吗?检查我的回复。
  • @Sefe:实验是在纯Ascii文本上进行的;所以文化不应该是这样。如果只有文本包含特定于文化的字母(比如部分在俄语、部分在格鲁吉亚语和中文上的字符串),那么文化可能是关键特征,并且在这种情况下ToUpper 可能会更快/更慢于ToLower
  • 好吧,将字符从一种情况转换为另一种情况的逻辑至少部分由特定于文化的代码执行。无论输入值如何,各自的实现都可能存在差异,从而导致不同的性能。
【解决方案2】:

您对ToUpperToLower 快的最初假设存在逻辑谬误。

您的转化文化敏感。您不是在执行序数操作,而是在执行取决于您当前文化的操作(由CultureInfo.CurrentCulture 返回)。从小写到大写的转换可能在您使用的文化中更快,而在另一种文化中可能更慢。在一种文化中的转换可能也比在另一种文化中的转换更快。

因此,您最初认为ToUpperToLower一个 性能的假设是错误的。

【讨论】:

  • 是的,要正确进行的实验应该指定培养;类似“为什么ToUpper(new CultureInfo("ka-Ge")) 显着faster then ToLower(new CultureInfo("ka-Ge")) ...”。并且有两个可能的答案(针对指定的文化):“......因为微软已经优化了执行大写比较的代码......”和“这是文化特定的行为”
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-04-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-15
  • 2019-11-02
  • 2011-07-15
相关资源
最近更新 更多