Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/templates/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在C#中迭代字符串中单个字符的最快方法是什么?_C#_String - Fatal编程技术网

在C#中迭代字符串中单个字符的最快方法是什么?

在C#中迭代字符串中单个字符的最快方法是什么?,c#,string,C#,String,题目就是问题。下面是我通过研究来回答这个问题的尝试。但我不相信我的无知研究,所以我仍然提出了这个问题(在C#中迭代字符串中的单个字符的最快方法是什么?) 有时,我想逐个遍历字符串中的字符,比如在解析嵌套标记时——这是一种非常有用的方法。我想知道最快的方法是迭代字符串中的各个字符,特别是非常大的字符串 我自己做了一系列测试,结果如下。然而,有许多读者对.NET CLR和C#编译器有更深入的了解,所以我不知道我是否遗漏了一些明显的东西,或者我是否在测试代码中犯了错误。因此,我恳请大家作出集体回应。如

题目就是问题。下面是我通过研究来回答这个问题的尝试。但我不相信我的无知研究,所以我仍然提出了这个问题(在C#中迭代字符串中的单个字符的最快方法是什么?)

有时,我想逐个遍历字符串中的字符,比如在解析嵌套标记时——这是一种非常有用的方法。我想知道最快的方法是迭代字符串中的各个字符,特别是非常大的字符串

我自己做了一系列测试,结果如下。然而,有许多读者对.NET CLR和C#编译器有更深入的了解,所以我不知道我是否遗漏了一些明显的东西,或者我是否在测试代码中犯了错误。因此,我恳请大家作出集体回应。如果有人了解字符串索引器的实际工作原理,那将非常有帮助。(它是一种C语言功能,在幕后编译成其他东西?还是CLR内置的东西?)

第一个使用流的方法直接取自线程的可接受答案:

测试

longString
是一个9910万字符的字符串,由89份C#语言规范的纯文本版本组成。显示了20次迭代的结果。在有“启动”时间的地方(比如方法#3中隐式创建的数组的第一次迭代),我单独测试了它,比如在第一次迭代后中断循环

结果

根据我的测试,使用ToCharArray()方法在char数组中缓存字符串是迭代整个字符串的最快方法。ToCharArray()方法是一项前期费用,后续对单个字符的访问速度略快于内置索引访问器

                                           milliseconds
                                ---------------------------------
 Method                         Startup  Iteration  Total  StdDev
------------------------------  -------  ---------  -----  ------
 1 index accessor                     0        602    602       3
 2 explicit convert ToCharArray     165        410    582       3
 3 foreach (c in string.ToCharArray)168        455    623       3
 4 StringReader                       0       1150   1150      25
 5 StreamWriter => Stream           405       1940   2345      20
 6 GetBytes() => StreamReader       385       2065   2450      35
 7 GetBytes() => BinaryReader       385       5465   5850      80
 8 foreach (c in string)              0        960    960       4
更新:根据@Eric的评论,这里是在更普通的1.1M字符字符串(一份C规范)上进行100次迭代的结果。索引器和字符数组仍然是最快的,其次是foreach(字符串中的字符),然后是流方法

                                           milliseconds
                                ---------------------------------
 Method                         Startup  Iteration  Total  StdDev
------------------------------  -------  ---------  -----  ------
 1 index accessor                     0        6.6    6.6    0.11
 2 explicit convert ToCharArray     2.4        5.0    7.4    0.30
 3 for(c in string.ToCharArray)     2.4        4.7    7.1    0.33
 4 StringReader                       0       14.0   14.0    1.21
 5 StreamWriter => Stream           5.3       21.8   27.1    0.46
 6 GetBytes() => StreamReader       4.4       23.6   28.0    0.65
 7 GetBytes() => BinaryReader       5.0       61.8   66.8    0.79
 8 foreach (c in string)              0       10.3   10.3    0.11     
使用的代码(单独测试;为了简洁起见一起显示)

//1索引访问器
int strLength=长字符串长度;
对于(int i=0;iStreamReader
int strLength=长字符串长度;
MemoryStream stream=新的MemoryStream();
StreamWriter writer=新StreamWriter(流);
writer.Write(longString);
writer.Flush();
流位置=0;
StreamReader str=新的StreamReader(流);
while(stream.PositionStreamReader
int strLength=长字符串长度;
MemoryStream stream=新的MemoryStream(Encoding.Unicode.GetBytes(longString));
StreamReader str=新的StreamReader(流);
while(stream.Position二进制读取器
int strLength=长字符串长度;
MemoryStream stream=新的MemoryStream(Encoding.Unicode.GetBytes(longString));
BinaryReader br=新的BinaryReader(stream,Encoding.Unicode);
while(stream.Position
接受的答复:

我将@CodeInChaos和Ben的注释解释如下:

fixed (char* pString = longString) {
    char* pChar = pString;
    for (int i = 0; i < strLength; i++) {
        c = *pChar ;
        pChar++;
    }
}
fixed(char*pString=longString){
char*pChar=pString;
对于(int i=0;i

短字符串上100次迭代的执行时间为4.4 ms,有<0.1 ms st dev.

不包括
foreach
的任何原因吗

foreach (char c in text)
{
    ...
}

顺便问一下,这真的会成为您的性能瓶颈吗?迭代本身占总运行时间的比例是多少?

如果速度真的很重要
for
foreach

for (int i = 0; i < text.Length; i++) {
   char ch = text[i];
   ...
}
for(int i=0;i
最快的答案是使用C++/CLI:

这种方法使用指针算法迭代字符串中的字符。没有副本,没有隐式范围检查,也没有每个元素的函数调用

通过编写不安全的C版本的
PtrToStringChars
,很可能从C获得(几乎C++/CLI不需要固定)相同的性能

比如:

unsafe char* PtrToStringContent(string s, out GCHandle pin)
{
    pin = GCHandle.Alloc(s, GCHandleType.Pinned);
    return (char*)pin.AddrOfPinnedObject().Add(System.Runtime.CompilerServices.RuntimeHelpers.OffsetToStringData).ToPointer();
}
请记住在之后调用
GCHandle.Free

CodeInChaos的评论指出C#为这一点提供了一种语法糖分:

fixed(char* pch = s) { ... }

这种人工测试是相当危险的。值得注意的是//2和//3版本的代码实际上从未对字符串进行索引。抖动优化器只是扔掉了代码,因为根本没有使用c变量。您只是在测量for()循环需要多长时间。除非查看生成的机器代码,否则无法真正看到这一点

将其更改为
c+=longString[i]
强制使用数组索引器


这当然是胡说八道。只评测真实代码。

如果微优化对您非常重要,那么试试这个。(为了简单起见,我假设输入字符串的长度是8的倍数)

不安全的void LoopString()
{
固定(char*p=longString)
{
煤焦c1、c2、c3、c4;
Int64 len=长字符串长度;
Int64*lptr=(Int64*)p;
Int64 l;
对于(int i=0;ifixed(char* pch = s) { ... }
unsafe void LoopString()
{
    fixed (char* p = longString)
    {
        char c1,c2,c3,c4;
        Int64 len = longString.Length;
        Int64* lptr = (Int64*)p;
        Int64 l;
        for (int i = 0; i < len; i+=8)
        {
            l = *lptr;
            c1 = (char)(l & 0xffff);
            c2 = (char)(l >> 16);
            c3 = (char)(l >> 32);
            c4 = (char)(l >> 48);
            lptr++;
        }
    }
}
          Method |      Mean |     Error |    StdDev |
---------------- |----------:|----------:|----------:|
        Indexing | 5.9712 us | 0.8738 us | 0.3116 us |
 IndexingOnArray | 8.2907 us | 0.8208 us | 0.2927 us |
  ForEachOnArray | 8.1919 us | 0.6505 us | 0.1690 us |
         ForEach | 5.6946 us | 0.0648 us | 0.0231 us |
          Unsafe | 7.2952 us | 1.1050 us | 0.3941 us |
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Horology;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;

namespace StringIterationBenchmark
{
    public class StringIteration
    {
        public static void Main(string[] args)
        {
            var config = new ManualConfig();

            config.Add(DefaultConfig.Instance);

            config.Add(Job.Default
                .WithLaunchCount(1)
                .WithIterationTime(TimeInterval.FromMilliseconds(500))
                .WithWarmupCount(3)
                .WithTargetCount(6)
            );

            BenchmarkRunner.Run<StringIteration>(config);
        }

        private readonly string _longString = BuildLongString();

        private static string BuildLongString()
        {
            var sb = new StringBuilder();
            var random = new Random();

            while (sb.Length < 10000)
            {
                char c = (char)random.Next(char.MaxValue);
                if (!Char.IsControl(c))
                    sb.Append(c);
            }

            return sb.ToString();
        }

        [Benchmark]
        public char Indexing()
        {
            char c = '\0';
            var longString = _longString;
            int strLength = longString.Length;

            for (int i = 0; i < strLength; i++)
            {
                c |= longString[i];
            }

            return c;
        }

        [Benchmark]
        public char IndexingOnArray()
        {
            char c = '\0';
            var longString = _longString;
            int strLength = longString.Length;
            char[] charArray = longString.ToCharArray();

            for (int i = 0; i < strLength; i++)
            {
                c |= charArray[i];
            }

            return c;
        }

        [Benchmark]
        public char ForEachOnArray()
        {
            char c = '\0';
            var longString = _longString;

            foreach (char item in longString.ToCharArray())
            {
                c |= item;
            }

            return c;
        }

        [Benchmark]
        public char ForEach()
        {
            char c = '\0';
            var longString = _longString;

            foreach (char item in longString)
            {
                c |= item;
            }

            return c;
        }

        [Benchmark]
        public unsafe char Unsafe()
        {
            char c = '\0';
            var longString = _longString;
            int strLength = longString.Length;

            fixed (char* p = longString)
            {
                var p1 = p;

                for (int i = 0; i < strLength; i++)
                {
                    c |= *p1;
                    p1++;
                }
            }

            return c;
        }
    }
}
//8 foreach (c in string)
foreach (char c in longString) { }