即使似乎满足所有条件,JIT编译器也不会内联方法

Vis*_*ent 7 c# jit inlining compiler-optimization

背景

在编写用于解析某些文本的类时,我需要能够获取特定字符位置的行号(换句话说,计算该字符之前发生的所有换行符)。

为了找到可能实现此目的的最有效代码,我建立了一些基准测试,这些测试表明Regex是最慢的方法,而手动迭代字符串是最快的方法。

以下是我目前的方法(10000次迭代:278毫秒):

private string text;

/// <summary>
/// Returns whether the specified character index is the end of a line.
/// </summary>
/// <param name="index">The index to check.</param>
/// <returns></returns>
private bool IsEndOfLine(int index)
{
    //Matches "\r" and "\n" (but not "\n" if it's preceded by "\r").
    char c = text[index];
    return c == '\r' || (c == '\n' && (index == 0 || text[index - 1] != '\r'));
}

/// <summary>
/// Returns the number of the line at the specified character index.
/// </summary>
/// <param name="index">The index of the character which's line number to get.</param>
/// <returns></returns>
public int GetLineNumber(int index)
{
    if(index < 0 || index > text.Length) { throw new ArgumentOutOfRangeException("index"); }

    int lineNumber = 1;
    int end = index;

    index = 0;
    while(index < end) {
        if(IsEndOfLine(index)) lineNumber++;
        index++;
    }

    return lineNumber;
}
Run Code Online (Sandbox Code Playgroud)

但是,在进行这些基准测试时,我记得方法调用有时可能会有点昂贵,因此我决定尝试将条件也IsEndOfLine()直接从if-statement移入内部GetLineNumber()

如我所料,此方法执行速度快两倍以上(10k迭代:112 ms):

while(index < end) {
    char c = text[index];
    if(c == '\r' || (c == '\n' && (index == 0 || text[index - 1] != '\r'))) lineNumber++;
    index++;
}
Run Code Online (Sandbox Code Playgroud)

问题

根据我的阅读,除非指定[2],否则JIT编译器不会(或至少没有)优化大小超过[ 32]字节的IL代码。但是,尽管将此属性应用于,但似乎没有发生内联。[MethodImplAttribute(MethodImplOptions.AggressiveInlining)]IsEndOfLine()

我能够找到的大部分讨论都来自较早的帖子/文章。在最新的文章中(2012年的[2]),作者显然使用成功地内联了34字节的函数MethodImplOptions.AggressiveInlining,这意味着如果满足所有其他条件,则该标志允许内联较大的IL代码。

使用以下代码测量我的方法的大小,发现它的长度为54个字节:

Console.WriteLine(this.GetType().GetMethod("IsEndOfLine").GetMethodBody().GetILAsByteArray().Length);
Run Code Online (Sandbox Code Playgroud)

在VS 2019中使用Dissasembly窗口显示以下汇编代码IsEndOfLine()(在Viewing Options中打开了C#源代码):

(配置:Release(x86),禁用Just My Code在模块加载时禁止JIT优化

--- [PATH REMOVED]\Performance Test - Find text line number\TextParser.cs 
    28:             char c = text[index];
001E19BA  in          al,dx  
001E19BB  mov         eax,dword ptr [ecx+4]  
001E19BE  cmp         edx,dword ptr [eax+4]  
001E19C1  jae         001E19FF  
001E19C3  movzx       eax,word ptr [eax+edx*2+8]  
    29:             return c == '\r' || (c == '\n' && (index == 0 || text[index - 1] != '\r'));
001E19C8  cmp         eax,0Dh  
001E19CB  je          001E19F8  
001E19CD  cmp         eax,0Ah  
001E19D0  jne         001E19F4  
001E19D2  test        edx,edx  
001E19D4  je          001E19ED  
001E19D6  dec         edx  
001E19D7  mov         eax,dword ptr [ecx+4]  
001E19DA  cmp         edx,dword ptr [eax+4]  
001E19DD  jae         001E19FF  
001E19DF  cmp         word ptr [eax+edx*2+8],0Dh  
001E19E5  setne       al  
001E19E8  movzx       eax,al  
001E19EB  pop         ebp  
001E19EC  ret  
001E19ED  mov         eax,1  
001E19F2  pop         ebp  
001E19F3  ret  
001E19F4  xor         eax,eax  
001E19F6  pop         ebp  
001E19F7  ret  
001E19F8  mov         eax,1  
001E19FD  pop         ebp  
001E19FE  ret  
001E19FF  call        70C2E2B0  
001E1A04  int         3  
Run Code Online (Sandbox Code Playgroud)

...以及以下循环代码GetLineNumber()

    63:             index = 0;
001E1950  xor         esi,esi  
    64:             while(index < end) {
001E1952  test        ebx,ebx  
001E1954  jle         001E196C  
001E1956  mov         ecx,edi  
001E1958  mov         edx,esi  
001E195A  call        dword ptr ds:[144E10h]  
001E1960  test        eax,eax  
001E1962  je          001E1967  
    65:                 if(IsEndOfLine(index)) lineNumber++;
001E1964  inc         dword ptr [ebp-10h]  
    66:                 index++;
001E1967  inc         esi  
    64:             while(index < end) {
001E1968  cmp         esi,ebx  
001E196A  jl          001E1956  
    67:             }
    68: 
    69:             return lineNumber;
001E196C  mov         eax,dword ptr [ebp-10h]  
001E196F  pop         ecx  
001E1970  pop         ebx  
001E1971  pop         esi  
001E1972  pop         edi  
001E1973  pop         ebp  
001E1974  ret  
Run Code Online (Sandbox Code Playgroud)

我不太擅长阅读汇编代码,但是在我看来,没有内联发生。

为什么IsEndOfLine()即使MethodImplOptions.AggressiveInlining指定了JIT编译器也不能内联我的方法?我知道这个标志仅一个提示给编译器,但基于[2]应用它应能内联IL 较大超过32个字节。除此之外,对我来说,我的代码似乎可以满足所有其他条件。

我还有其他限制吗?

基准测试

结果

private string text;

/// <summary>
/// Returns whether the specified character index is the end of a line.
/// </summary>
/// <param name="index">The index to check.</param>
/// <returns></returns>
private bool IsEndOfLine(int index)
{
    //Matches "\r" and "\n" (but not "\n" if it's preceded by "\r").
    char c = text[index];
    return c == '\r' || (c == '\n' && (index == 0 || text[index - 1] != '\r'));
}

/// <summary>
/// Returns the number of the line at the specified character index.
/// </summary>
/// <param name="index">The index of the character which's line number to get.</param>
/// <returns></returns>
public int GetLineNumber(int index)
{
    if(index < 0 || index > text.Length) { throw new ArgumentOutOfRangeException("index"); }

    int lineNumber = 1;
    int end = index;

    index = 0;
    while(index < end) {
        if(IsEndOfLine(index)) lineNumber++;
        index++;
    }

    return lineNumber;
}
Run Code Online (Sandbox Code Playgroud)

<基准代码移动到回答为简洁>

脚注

1 内联还是不内联:这就是问题所在

2 CLR 4.5 JIT中的积极内联


-编辑-

由于某种原因,在重新启动VS,启用和重新禁用之前提到的设置以及重新应用之后MethodImplOptions.AggressiveInlining,该方法现在似乎已内联。但是,它添加了一些if手动插入-condition 时不存在的说明。

JIT优化版本

    66:             while(index < end) {
001E194B  test        ebx,ebx  
001E194D  jle         001E1998  
001E194F  mov         esi,dword ptr [ecx+4]  
    67:                 if(IsEndOfLine(index)) lineNumber++;
001E1952  cmp         edx,esi  
001E1954  jae         001E19CA  
001E1956  movzx       eax,word ptr [ecx+edx*2+8]  

001E195B  cmp         eax,0Dh  
001E195E  je          001E1989  
001E1960  cmp         eax,0Ah  
001E1963  jne         001E1985  
001E1965  test        edx,edx  
001E1967  je          001E197E  
001E1969  mov         eax,edx  
001E196B  dec         eax  
001E196C  cmp         eax,esi  
001E196E  jae         001E19CA  
001E1970  cmp         word ptr [ecx+eax*2+8],0Dh  
001E1976  setne       al  
001E1979  movzx       eax,al  
001E197C  jmp         001E198E  
001E197E  mov         eax,1  
001E1983  jmp         001E198E  
001E1985  xor         eax,eax  
001E1987  jmp         001E198E  
001E1989  mov         eax,1  
001E198E  test        eax,eax  
001E1990  je          001E1993  
001E1992  inc         edi  
    68:                 index++;
Run Code Online (Sandbox Code Playgroud)

我的优化版本

    87:             while(index < end) {
001E1E9B  test        ebx,ebx  
001E1E9D  jle         001E1ECE  
001E1E9F  mov         esi,dword ptr [ecx+4]  
    88:                 char c = text[index];
001E1EA2  cmp         edx,esi  
001E1EA4  jae         001E1F00  
001E1EA6  movzx       eax,word ptr [ecx+edx*2+8]  
    89:                 if(c == '\r' || (c == '\n' && (index == 0 || text[index - 1] != '\r'))) lineNumber++;
001E1EAB  cmp         eax,0Dh  
001E1EAE  je          001E1EC8  
001E1EB0  cmp         eax,0Ah  
001E1EB3  jne         001E1EC9  
001E1EB5  test        edx,edx  
001E1EB7  je          001E1EC8  
001E1EB9  mov         eax,edx  
001E1EBB  dec         eax  
001E1EBC  cmp         eax,esi  
001E1EBE  jae         001E1F00  
001E1EC0  cmp         word ptr [ecx+eax*2+8],0Dh  
001E1EC6  je          001E1EC9  
001E1EC8  inc         edi  
    90:                 index++;
Run Code Online (Sandbox Code Playgroud)

新说明

001E1976  setne       al  
001E1979  movzx       eax,al  
001E197C  jmp         001E198E  
001E197E  mov         eax,1  
001E1983  jmp         001E198E  
001E1985  xor         eax,eax  
001E1987  jmp         001E198E  
001E1989  mov         eax,1  
001E198E  test        eax,eax  
Run Code Online (Sandbox Code Playgroud)

我仍然看不到性能/执行速度上的任何改善,但是...据推测,这是由于JIT添加了额外的指令所致,我想这在没有内联条件的情况下还是一样好吗?

Vis*_*ent 2

由于某种原因,在重新启动 VS、启用和重新禁用前面提到的设置以及重新应用后MethodImplOptions.AggressiveInlining,该方法现在似乎是内联的(奇怪的是之前没有......)。但是,它添加了一些当您手动内联条件时不存在的指令if

\n\n

然而,效率/执行速度似乎保持不变。Hans Passant 建议我将短路运算符(如果可能)替换为常规的|&,这确实将速度差距从 2 倍减少到 1.5 倍。我猜这在 JIT 优化方面已经是最好的了。

\n\n
return c == \'\\r\' | (c == \'\\n\' & (index == 0 || text[index - 1] != \'\\r\'));\n
Run Code Online (Sandbox Code Playgroud)\n\n

我做出的一个有趣的发现(或者至少对我来说很有趣,因为我并不真正理解这些程序集级优化在幕后如何工作)是,当对手动内联条件(内部GetLineNumberInline())进行相同的操作符交换时,执行速度变得更差。

\n\n

这次冒险的目的是获得尽可能高效的代码,而不必在我使用它的任何地方重复它(因为在IsEndOfLine()整个项目中多次使用原始代码)。最后我想我会坚持IsEndOfLine()只在内部复制代码GetLineNumber(),因为事实证明这在执行速度方面是最快的。

\n\n
\n\n

我要感谢那些花时间试图帮助我的人(一些评论已被删除),虽然我没有达到我认为 JIT 优化内联会给我带来的结果,但我仍然学到了很多我以前不知道的。现在我至少稍微了解了 JIT 优化在幕后的作用以及它如何比我最初想象的复杂得多。

\n\n
\n\n

完整的基准测试结果,以供将来参考(按执行时间排序):

\n\n
\n文本长度: 15882\n字符位置: 11912\n\n标准循环(内联):                     00:00:00.1429526 (10000 \xc3\xa0 0.0142 ms) \n标准循环(内联不安全): 00:00:00.1642801 (10000 \xc3 \xa0 0.0164 ms)\n标准循环 (内联 + 无短路): 00:00:00.3250843 (10000 \xc3\xa0 0.0325 ms)\n标准循环 (AggressiveInlined): 00:00:00.3318966 (10000 \xc3\xa0 0.0331 ms)\n标准循环(不安全): 00:00:00.3605394 (10000 \xc3\xa0 0.0360 ms)\n标准循环: 00:00:00.3859629 (10000 \xc3\xa0 0.0385 ms)\n正则表达式 (子字符串): 00:00 :01.8794045 (10000 \xc3\xa0 0.1879 ms)\nRegex (MatchCollection 循环): 00:00:02.4916785 (10000 \xc3\xa0 0.2491 ms)\n\n结果行: 284\n\n/* “不安全”正在使用访问字符串字符的指针 */\n
\n\n
\n\n
class Program\n{\n    const int RUNS = 10000;\n\n    static void Main(string[] args)\n    {\n        string text = "";\n        Random r = new Random();\n\n        //Some words to fill the string with.\n        string[] words = new string[] { "Hello", "world", "Inventory.MaxAmount 32", "+QUICKTORETALIATE", "TNT1 AABBCC 6 A_JumpIf(ACS_ExecuteWithResult(460, 0, 0, 0) == 0, \\"See0\\")" };\n\n        //Various line endings.\n        string[] endings = new string[] { "\\r\\n", "\\r", "\\n" };\n\n\n\n        /*\n            Generate text\n        */\n        int lineCount = r.Next(256, 513);\n\n        for(int l = 0; l < lineCount; l++) {\n            int wordCount = r.Next(1, 4);\n            text += new string(\' \', r.Next(4, 9));\n\n            for(int w = 0; w < wordCount; w++) {\n                text += words[wordCount] + (w < wordCount - 1 ? " " : "");\n            }\n\n            text += endings[r.Next(0, endings.Length)];\n        }\n\n        Console.WriteLine("Text length: " + text.Length);\n        Console.WriteLine();\n\n\n\n        /*\n            Initialize class and stopwatch\n        */\n        TextParser parser = new TextParser(text);\n        Stopwatch sw = new Stopwatch();\n\n        List<int> numbers = new List<int>(); //Using a list to prevent the compiler from optimizing-away the "GetLineNumber" call.\n\n\n\n        /*\n            Test 1 - Standard loop\n        */\n        sw.Restart();\n        for(int x = 0; x < RUNS; x++) {\n            numbers.Add(parser.GetLineNumber((int)(text.Length * 0.75) + r.Next(-4, 4)));\n        }\n        sw.Stop();\n\n        Console.WriteLine("Line: " + numbers[0]);\n        Console.WriteLine("Standard loop: ".PadRight(41) + sw.Elapsed.ToString() + " (" + numbers.Count + " \xc3\xa0 " + new TimeSpan(sw.Elapsed.Ticks / numbers.Count).TotalMilliseconds.ToString() + " ms)");\n        Console.WriteLine();\n\n        numbers = new List<int>();\n\n\n\n        /*\n            Test 2 - Standard loop (with AggressiveInlining)\n        */\n        sw.Restart();\n        for(int x = 0; x < RUNS; x++) {\n            numbers.Add(parser.GetLineNumber2((int)(text.Length * 0.75) + r.Next(-4, 4)));\n        }\n        sw.Stop();\n\n        Console.WriteLine("Line: " + numbers[0]);\n        Console.WriteLine("Standard loop (AggressiveInlining): ".PadRight(41) + sw.Elapsed.ToString() + " (" + numbers.Count + " \xc3\xa0 " + new TimeSpan(sw.Elapsed.Ticks / numbers.Count).TotalMilliseconds.ToString() + " ms)");\n        Console.WriteLine();\n\n        numbers = new List<int>();\n\n\n\n        /*\n            Test 3 - Standard loop (with inline check)\n        */\n        sw.Restart();\n        for(int x = 0; x < RUNS; x++) {\n            numbers.Add(parser.GetLineNumberInline((int)(text.Length * 0.75) + r.Next(-4, 4)));\n        }\n        sw.Stop();\n\n        Console.WriteLine("Line: " + numbers[0]);\n        Console.WriteLine("Standard loop (inline): ".PadRight(41) + sw.Elapsed.ToString() + " (" + numbers.Count + " \xc3\xa0 " + new TimeSpan(sw.Elapsed.Ticks / numbers.Count).TotalMilliseconds.ToString() + " ms)");\n        Console.WriteLine();\n\n        numbers = new List<int>();\n\n\n\n        /*\n            Test 4 - Standard loop (with inline and no short-circuiting)\n        */\n        sw.Restart();\n        for(int x = 0; x < RUNS; x++) {\n            numbers.Add(parser.GetLineNumberInline2((int)(text.Length * 0.75) + r.Next(-4, 4)));\n        }\n        sw.Stop();\n\n        Console.WriteLine("Line: " + numbers[0]);\n        Console.WriteLine("Standard loop (inline + no short-circuit): ".PadRight(41) + sw.Elapsed.ToString() + " (" + numbers.Count + " \xc3\xa0 " + new TimeSpan(sw.Elapsed.Ticks / numbers.Count).TotalMilliseconds.ToString() + " ms)");\n        Console.WriteLine();\n\n        numbers = new List<int>();\n\n\n\n        /*\n            Test 5 - Standard loop (with unsafe check)\n        */\n        sw.Restart();\n        for(int x = 0; x < RUNS; x++) {\n            numbers.Add(parser.GetLineNumberUnsafe((int)(text.Length * 0.75) + r.Next(-4, 4)));\n        }\n        sw.Stop();\n\n        Console.WriteLine("Line: " + numbers[0]);\n        Console.WriteLine("Standard loop (unsafe): ".PadRight(41) + sw.Elapsed.ToString() + " (" + numbers.Count + " \xc3\xa0 " + new TimeSpan(sw.Elapsed.Ticks / numbers.Count).TotalMilliseconds.ToString() + " ms)");\n        Console.WriteLine();\n\n        numbers = new List<int>();\n\n\n\n        /*\n            Test 6 - Standard loop (with inline + unsafe check)\n        */\n        sw.Restart();\n        for(int x = 0; x < RUNS; x++) {\n            numbers.Add(parser.GetLineNumberUnsafeInline((int)(text.Length * 0.75) + r.Next(-4, 4)));\n        }\n        sw.Stop();\n\n        Console.WriteLine("Line: " + numbers[0]);\n        Console.WriteLine("Standard loop (inline unsafe): ".PadRight(41) + sw.Elapsed.ToString() + " (" + numbers.Count + " \xc3\xa0 " + new TimeSpan(sw.Elapsed.Ticks / numbers.Count).TotalMilliseconds.ToString() + " ms)");\n        Console.WriteLine();\n\n        numbers = new List<int>();\n\n\n\n        /*\n            Test 7 - Regex (with Substring)\n        */\n        sw.Restart();\n        for(int x = 0; x < RUNS; x++) {\n            numbers.Add(parser.GetLineNumberRegex((int)(text.Length * 0.75) + r.Next(-4, 4)));\n        }\n        sw.Stop();\n\n        Console.WriteLine("Line: " + numbers[0]);\n        Console.WriteLine("Regex (Substring): ".PadRight(41) + sw.Elapsed.ToString() + " (" + numbers.Count + " \xc3\xa0 " + new TimeSpan(sw.Elapsed.Ticks / numbers.Count).TotalMilliseconds.ToString() + " ms)");\n        Console.WriteLine();\n\n        numbers = new List<int>();\n\n\n\n        /*\n            Test 8 - Regex (with MatchCollection loop)\n        */\n        sw.Restart();\n        for(int x = 0; x < RUNS; x++) {\n            numbers.Add(parser.GetLineNumberRegex2((int)(text.Length * 0.75) + r.Next(-4, 4)));\n        }\n        sw.Stop();\n\n        Console.WriteLine("Line: " + numbers[0]);\n        Console.WriteLine("Regex (MatchCollection loop): ".PadRight(41) + sw.Elapsed.ToString() + " (" + numbers.Count + " \xc3\xa0 " + new TimeSpan(sw.Elapsed.Ticks / numbers.Count).TotalMilliseconds.ToString() + " ms)");\n        Console.WriteLine();\n\n        numbers = new List<int>();\n\n\n\n        /*\n            Tests completed\n        */\n        Console.Write("All tests completed. Press ENTER to close...");\n        while(Console.ReadKey(true).Key != ConsoleKey.Enter);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n\n\n
public class TextParser\n{\n    private static readonly Regex LineRegex = new Regex("\\r\\n|\\r|\\n", RegexOptions.Compiled);\n\n    private string text;\n\n    public TextParser(string text)\n    {\n        this.text = text;\n    }\n\n    /// <summary>\n    /// Returns whether the specified character index is the end of a line.\n    /// </summary>\n    /// <param name="index">The index to check.</param>\n    /// <returns></returns>\n    private bool IsEndOfLine(int index)\n    {\n        char c = text[index];\n        return c == \'\\r\' || (c == \'\\n\' && (index == 0 || text[index - 1] != \'\\r\'));\n    }\n\n    /// <summary>\n    /// Returns whether the specified character index is the end of a line.\n    /// </summary>\n    /// <param name="index">The index to check.</param>\n    /// <returns></returns>\n    [MethodImpl(MethodImplOptions.AggressiveInlining)]\n    private bool IsEndOfLineAggressiveInlining(int index)\n    {\n        char c = text[index];\n        return c == \'\\r\' || (c == \'\\n\' && (index == 0 || text[index - 1] != \'\\r\'));\n    }\n\n    /// <summary>\n    /// Returns whether the specified character index is the end of a line.\n    /// </summary>\n    /// <param name="index">The index to check.</param>\n    /// <returns></returns>\n    private bool IsEndOfLineUnsafe(int index)\n    {\n        unsafe\n        {\n            fixed(char* ptr = text) {\n                char c = ptr[index];\n                return c == \'\\r\' || (c == \'\\n\' && (index == 0 || ptr[index - 1] != \'\\r\'));\n            }\n        }\n    }\n\n\n\n    /// <summary>\n    /// Returns the number of the line at the specified character index.\n    /// </summary>\n    /// <param name="index">The index of the character which\'s line number to get.</param>\n    /// <returns></returns>\n    public int GetLineNumber(int index)\n    {\n        if(index < 0 || index > text.Length) { throw new ArgumentOutOfRangeException("index"); }\n\n        int lineNumber = 1;\n        int end = index;\n\n        index = 0;\n        while(index < end) {\n            if(IsEndOfLine(index)) lineNumber++;\n            index++;\n        }\n\n        return lineNumber;\n    }\n\n\n\n    /// <summary>\n    /// Returns the number of the line at the specified character index.\n    /// </summary>\n    /// <param name="index">The index of the character which\'s line number to get.</param>\n    /// <returns></returns>\n    public int GetLineNumber2(int index)\n    {\n        if(index < 0 || index > text.Length) { throw new ArgumentOutOfRangeException("index"); }\n\n        int lineNumber = 1;\n        int end = index;\n\n        index = 0;\n        while(index < end) {\n            if(IsEndOfLineAggressiveInlining(index)) lineNumber++;\n            index++;\n        }\n\n        return lineNumber;\n    }\n\n    /// <summary>\n    /// Returns the number of the line at the specified character index.\n    /// </summary>\n    /// <param name="index">The index of the character which\'s line number to get.</param>\n    /// <returns></returns>\n    public int GetLineNumberInline(int index)\n    {\n        if(index < 0 || index > text.Length) { throw new ArgumentOutOfRangeException("index"); }\n\n        int lineNumber = 1;\n        int end = index;\n\n        index = 0;\n        while(index < end) {\n            char c = text[index];\n            if(c == \'\\r\' || (c == \'\\n\' && (index == 0 || text[index - 1] != \'\\r\'))) lineNumber++;\n            index++;\n        }\n\n        return lineNumber;\n    }\n\n    /// <summary>\n    /// Returns the number of the line at the specified character index.\n    /// </summary>\n    /// <param name="index">The index of the character which\'s line number to get.</param>\n    /// <returns></returns>\n    public int GetLineNumberInline2(int index)\n    {\n        if(index < 0 || index > text.Length) { throw new ArgumentOutOfRangeException("index"); }\n\n        int lineNumber = 1;\n        int end = index;\n\n        index = 0;\n        while(index < end) {\n            char c = text[index];\n            if(c == \'\\r\' | (c == \'\\n\' & (index == 0 || text[index - 1] != \'\\r\'))) lineNumber++;\n            index++;\n        }\n\n        return lineNumber;\n    }\n\n    /// <summary>\n    /// Returns the number of the line at the specified character index.\n    /// </summary>\n    /// <param name="index">The index of the character which\'s line number to get.</param>\n    /// <returns></returns>\n    public int GetLineNumberUnsafe(int index)\n    {\n        if(index < 0 || index > text.Length) { throw new ArgumentOutOfRangeException("index"); }\n\n        int lineNumber = 1;\n        int end = index;\n\n        index = 0;\n        while(index < end) {\n            if(IsEndOfLineUnsafe(index)) lineNumber++;\n            index++;\n        }\n\n        return lineNumber;\n    }\n\n    /// <summary>\n    /// Returns the number of the line at the specified character index.\n    /// </summary>\n    /// <param name="index">The index of the character which\'s line number to get.</param>\n    /// <returns></returns>\n    public int GetLineNumberUnsafeInline(int index)\n    {\n        if(index < 0 || index > text.Length) { throw new ArgumentOutOfRangeException("index"); }\n\n        int lineNumber = 1;\n        int end = index;\n\n        unsafe\n        {\n            fixed(char* ptr = text) {\n                index = 0;\n                while(index < end) {\n                    char c = ptr[index];\n                    if(c == \'\\r\' || (c == \'\\n\' && (index == 0 || ptr[index - 1] != \'\\r\'))) lineNumber++;\n                    index++;\n                }\n            }\n        }\n\n        return lineNumber;\n    }\n\n    /// <summary>\n    /// Returns the number of the line at the specified character index. Utilizes a Regex.\n    /// </summary>\n    /// <param name="index">The index of the character which\'s line number to get.</param>\n    /// <returns></returns>\n    public int GetLineNumberRegex(int index)\n    {\n        return LineRegex.Matches(text.Substring(0, index)).Count + 1;\n    }\n\n    /// <summary>\n    /// Returns the number of the line at the specified character index. Utilizes a Regex.\n    /// </summary>\n    /// <param name="index">The index of the character which\'s line number to get.</param>\n    /// <returns></returns>\n    public int GetLineNumberRegex2(int index)\n    {\n        int lineNumber = 1;\n        MatchCollection mc = LineRegex.Matches(text);\n\n        for(int y = 0; y < mc.Count; y++) {\n            if(mc[y].Index >= index) break;\n            lineNumber++;\n        }\n\n        return lineNumber;\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n