5 вариантов unescape - угадайте какой самый быстрый (неожида
От: Shmj Ниоткуда  
Дата: 29.11.19 23:09
Оценка:
Нужно чтобы в строке не было символа '\n'. Для этого:

1. Заменяем все "\\" на "\\\\".
2. Заменяем все "\n" на "\\n".

Это все понятно. Самый смак — как возвернуть все взад...

Накидал 5 вариантов, см. полный код:

class Program
    {
        static void Main(string[] args)
        {
            Random r = new Random();

            var sb = new StringBuilder(2000000 * 7);

            sb.Append("test1\ntest2\\n\\\\test3");

            for (int i = 0; i < 2000000; i++)
            {
                if (r.Next(1, 3) == 1)
                    sb.Append("\\");

                if (r.Next(1, 3) == 1)
                    sb.Append("\\");

                if (r.Next(1, 3) == 1)
                    sb.Append("\\n");

                if (r.Next(1, 4) == 3)
                    sb.Append("\n");

                sb.Append("word");
            }

            var input = sb.ToString();
            
            var escaped = input.Replace("\\", "\\\\").Replace("\n", "\\n");

            var sw = new Stopwatch();
            
            sw.Reset();
            sw.Start();

            //var unescaped = Test1(escaped);
            //var unescaped = Test2(escaped);
            //var unescaped = Test3(escaped);
            //var unescaped = Test4(escaped);
            var unescaped = Test5(escaped);

            sw.Stop();

            Console.WriteLine(sw.ElapsedMilliseconds);
            Console.ReadLine();
            Console.WriteLine(unescaped.Equals(input));
        }

        static string Test1(string escaped)
        {
            var guid = Guid.NewGuid().ToString();
            return escaped.Replace("\\\\", guid).Replace("\\n", "\n").Replace(guid, "\\");
        }

        static string Test2(string escaped)
        {
            return escaped.Split(new[] {"\\\\"}, StringSplitOptions.None)
                .Aggregate((seed, next) => seed + "\\" + next.Replace("\\n", "\n"));
        }

        static string Test3(string escaped)
        {
            var parts = escaped.Split(new[] { "\\\\" }, StringSplitOptions.None).Select(part => part.Replace("\\n", "\n"));
            return string.Join("\\", parts);
        }

        static string Test4(string escaped)
        {
            var unescaped = new StringBuilder(escaped.Length);

            bool slash = false;

            foreach (var ch in escaped)
            {
                if (slash)
                {
                    if ('\\' == ch)
                        unescaped.Append('\\');

                    if ('n' == ch)
                        unescaped.Append('\n');

                    slash = false;
                    continue;
                }

                if ('\\' == ch)
                    slash = true;
                else
                    unescaped.Append(ch);
            }

            return unescaped.ToString();
        }

        static string Test5(string escaped)
        {
            var unescaped = new StringBuilder(escaped.Length);

            int currentIndex = 0;

            while (true)
            {
                var slashIndex = escaped.IndexOf('\\', currentIndex);

                if (-1 == slashIndex)
                {
                    unescaped.Append(escaped, currentIndex, escaped.Length - currentIndex);
                    break;
                }

                unescaped.Append(escaped, currentIndex, slashIndex - currentIndex);
                currentIndex = slashIndex;

                var nextChar = escaped[slashIndex + 1];

                switch (nextChar)
                {
                    case '\\':
                        unescaped.Append('\\');
                        break;
                    case 'n':
                        unescaped.Append('\n');
                        break;
                    default:
                        throw new InvalidOperationException("nextChar=" + nextChar);
                }

                currentIndex++;
                currentIndex++;
            }

            return unescaped.ToString();
        }
    }


Сокращенная версия для запуска: https://dotnetfiddle.net/52uORv

И вопрос: как вы думаете, какой вариант самый быстрый? Как сделать быстрее?
Отредактировано 29.11.2019 23:13 Shmj . Предыдущая версия .
escape
Re: 5 вариантов unescape - угадайте какой самый быстрый (нео
От: Sharowarsheg  
Дата: 29.11.19 23:47
Оценка:
Здравствуйте, Shmj, Вы писали:

S>Нужно чтобы в строке не было символа '\n'. Для этого:

S>Накидал 5 вариантов, см. полный код:

S>
S>class Program
S>    {
            
S>            var escaped = input.Replace("\\", "\\\\").Replace("\n", "\\n");

S>            var sw = new Stopwatch();
            
S>            sw.Reset();
S>            sw.Start();

S>            //var unescaped = Test1(escaped);
S>            //var unescaped = Test2(escaped);
S>            //var unescaped = Test3(escaped);
S>            //var unescaped = Test4(escaped);
S>            var unescaped = Test5(escaped);

S>            sw.Stop();

S>            Console.WriteLine(sw.ElapsedMilliseconds);
S>            Console.ReadLine();
S>            Console.WriteLine(unescaped.Equals(input));
S>        }

S>И вопрос: как вы думаете, какой вариант самый быстрый? Как сделать быстрее?

Она что у тебя там, всего один раз выполняется?
Отредактировано 29.11.2019 23:56 Sharowarsheg . Предыдущая версия .
Re[2]: 5 вариантов unescape - угадайте какой самый быстрый (нео
От: Shmj Ниоткуда  
Дата: 29.11.19 23:58
Оценка:
Здравствуйте, Sharowarsheg, Вы писали:

S>Она что у тебя там, всего один раз выполняется?


Один раз, но строка достаточно длинная.
Re[3]: 5 вариантов unescape - угадайте какой самый быстрый (
От: Sharowarsheg  
Дата: 30.11.19 00:42
Оценка:
Здравствуйте, Shmj, Вы писали:

S>Здравствуйте, Sharowarsheg, Вы писали:


S>>Она что у тебя там, всего один раз выполняется?


S>Один раз, но строка достаточно длинная.


Нет, нужно в тыщу раз больше, по крайней мере. Кроме того, это разные режимы — один раз и очень длинная строка, или много раз маленькие строки. В любом случае, самый быстрый будет тот, в котором больше всего for int i=0.... и меньше всего всяких прогрессивных LINQ и проча. Не исключено, что еще быстрее будет переделать строку в массив слов Uint16 и с ними работать.
Отредактировано 30.11.2019 0:47 Sharowarsheg . Предыдущая версия .
Re: 5 вариантов unescape - угадайте какой самый быстрый (неожида
От: adetkov Россия  
Дата: 30.11.19 01:52
Оценка: 3 (1)
Здравствуйте, Shmj, Вы писали:

S>И вопрос: как вы думаете, какой вариант самый быстрый? Как сделать быстрее?


Есть еще такой вариант:
private static string Unescape(string str)
{
    char[] array = str.ToCharArray();
    int cur = 0;
    bool waitControl = false;

    for (int i = 0; i < array.Length; i++)
    {
        if (!waitControl)
        {
            if (array[i] == '\\')
            {
                waitControl = true;
                continue;
            }

            array[cur++] = array[i];
            continue;
        }

        waitControl = false;
        array[cur++] = array[i] switch
        {
            'n' => '\n',
            '\\' => '\\',
            _ => throw new Exception("Unexpected control symbol")
        };
    }

    return new string(array, 0, cur);
}
Re[2]: 5 вариантов unescape - угадайте какой самый быстрый (неожида
От: Shmj Ниоткуда  
Дата: 30.11.19 02:04
Оценка:
Здравствуйте, adetkov, Вы писали:

S>>И вопрос: как вы думаете, какой вариант самый быстрый? Как сделать быстрее?


A>Есть еще такой вариант:


По логике похож на Test4.
Re[2]: 5 вариантов unescape - угадайте какой самый быстрый (неожида
От: Shmj Ниоткуда  
Дата: 30.11.19 02:09
Оценка:
Здравствуйте, adetkov, Вы писали:

A>Есть еще такой вариант:


А теперь сравните его по скорости с вариантом Test5. Как думаете — какой быстрее?
Re[3]: 5 вариантов unescape - угадайте какой самый быстрый (неожида
От: adetkov Россия  
Дата: 30.11.19 02:33
Оценка:
Здравствуйте, Shmj, Вы писали:

S>А теперь сравните его по скорости с вариантом Test5. Как думаете — какой быстрее?


BenchmarkDotNet=v0.12.0, OS=Windows 8.1 (6.3.9600.0)

Intel Core i7-4510U CPU 2.00GHz (Haswell), 1 CPU, 4 logical and 2 physical cores

Frequency=2539066 Hz, Resolution=393.8456 ns, Timer=TSC
.NET Core SDK=3.0.100
  [Host]     : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214),
X64 RyuJIT
  DefaultJob : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214),
X64 RyuJIT


|   Method |      Mean |    Error |   StdDev |
|--------- |----------:|---------:|---------:|
| Unescape |  74.69 ms | 1.471 ms | 2.063 ms |
|    Test5 | 126.85 ms | 2.401 ms | 2.128 ms |
Re[4]: 5 вариантов unescape - угадайте какой самый быстрый (
От: Shmj Ниоткуда  
Дата: 30.11.19 02:58
Оценка:
Здравствуйте, adetkov, Вы писали:

A>| Method | Mean | Error | StdDev |

A>|--------- |----------:|---------:|---------:|
A>| Unescape | 74.69 ms | 1.471 ms | 2.063 ms |
A>| Test5 | 126.85 ms | 2.401 ms | 2.128 ms |

Странно, у меня другие результаты:

  Скрытый текст
Test6=206
Test5=184

Test6=194
Test5=200

Test6=205
Test5=182

Test6=209
Test5=181

Test6=204
Test5=184

Test6=204
Test5=186

Test6=206
Test5=187

Test6=208
Test5=272

Test6=223
Test5=176

Test6=206
Test5=189


Test5 практически всегда быстре.

Полный код:

  Скрытый текст
using System;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Random r = new Random();

            var sb = new StringBuilder(2000000 * 7);

            sb.Append("test1\ntest2\\n\\\\test3");

            for (int i = 0; i < 2000000; i++)
            {
                if (r.Next(1, 3) == 1)
                    sb.Append("\\");

                if (r.Next(1, 3) == 1)
                    sb.Append("\\");

                if (r.Next(1, 3) == 1)
                    sb.Append("\\n");

                if (r.Next(1, 4) == 3)
                    sb.Append("\n");

                sb.Append("word");
            }

            var input = sb.ToString();

            var escaped = input.Replace("\\", "\\\\").Replace("\n", "\\n");

            var sw = new Stopwatch();


            for (int i = 0; i < 10; i++)
            {
                sw.Reset();
                sw.Start();

                Test6(escaped);
                sw.Stop();
                Console.WriteLine("Test6=" + sw.ElapsedMilliseconds);


                sw.Reset();
                sw.Start();

                Test5(escaped);
                sw.Stop();
                Console.WriteLine("Test5=" + sw.ElapsedMilliseconds);

                Console.WriteLine();
            }

            sw.Reset();
            sw.Start();

            //var unescaped = Test1(escaped); // 1235
            //var unescaped = Test2(escaped); mamma mia!
            //var unescaped = Test3(escaped); // 891
            //var unescaped = Test4(escaped); // 289
            //var unescaped = Test5(escaped); // 183
            var unescaped = Test6(escaped); // 208

            sw.Stop();

            Console.WriteLine(sw.ElapsedMilliseconds);
            Console.ReadLine();
            Console.WriteLine(unescaped.Equals(input));
        }

        static string Test1(string escaped)
        {
            var guid = Guid.NewGuid().ToString();
            return escaped.Replace("\\\\", guid).Replace("\\n", "\n").Replace(guid, "\\");
        }

        static string Test2(string escaped)
        {
            return escaped.Split(new[] {"\\\\"}, StringSplitOptions.None)
                .Aggregate((seed, next) => seed + "\\" + next.Replace("\\n", "\n"));
        }

        static string Test3(string escaped)
        {
            var parts = escaped.Split(new[] {"\\\\"}, StringSplitOptions.None)
                .Select(part => part.Replace("\\n", "\n"));
            return string.Join("\\", parts);
        }

        static string Test4(string escaped)
        {
            var unescaped = new StringBuilder(escaped.Length);

            bool slash = false;

            foreach (var ch in escaped)
            {
                if (slash)
                {
                    if ('\\' == ch)
                        unescaped.Append('\\');

                    if ('n' == ch)
                        unescaped.Append('\n');

                    slash = false;
                    continue;
                }

                if ('\\' == ch)
                    slash = true;
                else
                    unescaped.Append(ch);
            }

            return unescaped.ToString();
        }

        static string Test5(string escaped)
        {
            var unescaped = new StringBuilder(escaped.Length);

            int currentIndex = 0;

            while (true)
            {
                var slashIndex = escaped.IndexOf('\\', currentIndex);

                if (-1 == slashIndex)
                {
                    unescaped.Append(escaped, currentIndex, escaped.Length - currentIndex);
                    break;
                }

                unescaped.Append(escaped, currentIndex, slashIndex - currentIndex);
                currentIndex = slashIndex;

                var nextChar = escaped[slashIndex + 1];

                switch (nextChar)
                {
                    case '\\':
                        unescaped.Append('\\');
                        break;
                    case 'n':
                        unescaped.Append('\n');
                        break;
                    default:
                        throw new InvalidOperationException("nextChar=" + nextChar);
                }

                currentIndex++;
                currentIndex++;
            }

            return unescaped.ToString();
        }

        private static string Test6(string str)
        {
            char[] array = str.ToCharArray();
            int cur = 0;
            bool waitControl = false;

            for (int i = 0; i < array.Length; i++)
            {
                if (!waitControl)
                {
                    if (array[i] == '\\')
                    {
                        waitControl = true;
                        continue;
                    }

                    array[cur++] = array[i];
                    continue;
                }

                waitControl = false;


                switch (array[i])
                {
                    case 'n':
                        array[cur++] = '\n';
                        break;
                    case '\\':
                        array[cur++] = '\\';
                        break;
                    default:
                        throw new Exception("Unexpected control symbol");
                }
            }

            return new string(array, 0, cur);
        }
    }
}


Вы какие данные подаете на вход?
Отредактировано 30.11.2019 3:07 Shmj . Предыдущая версия .
Re[5]: 5 вариантов unescape - угадайте какой самый быстрый (
От: adetkov Россия  
Дата: 30.11.19 03:15
Оценка:
Здравствуйте, Shmj, Вы писали:

S>Вы какие данные подаете на вход?


Проверка скорости исполнения штука достаточно тонкая, я для этих целей использую специальную библиотеку. Программу запускаю в версии Release

  Benchmark
using System.Text;
using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

public class Test
{
    private string _test;

    public Test()
    {
        Random r = new Random();

        var sb = new StringBuilder(2000000 * 7);

        sb.Append("test1\ntest2\\n\\\\test3");

        for (int i = 0; i < 2000000; i++)
        {
            if (r.Next(1, 3) == 1)
                sb.Append("\\");

            if (r.Next(1, 3) == 1)
                sb.Append("\\");

            if (r.Next(1, 3) == 1)
                sb.Append("\\n");

            if (r.Next(1, 4) == 3)
                sb.Append("\n");

            sb.Append("word");
        }

        var input = sb.ToString();

        _test = input.Replace("\\", "\\\\").Replace("\n", "\\n");
    }

    [Benchmark(Description = "Unescape")]
    public string Unescape()
    {
        string str = _test;
        char[] array = str.ToCharArray();
        int cur = 0;
        bool waitControl = false;

        for (int i = 0; i < array.Length; i++)
        {
            if (!waitControl)
            {
                if (array[i] == '\\')
                {
                    waitControl = true;
                    continue;
                }

                array[cur++] = array[i];
                continue;
            }

            waitControl = false;
            array[cur++] = array[i] switch
            {
                'n' => '\n',
                '\\' => '\\',
                _ => throw new Exception("Unexpected symbol")
            };
        }

        return new string(array, 0, cur);
    }

    [Benchmark(Description = "Test5")]
    public string Test5()
    {
        string escaped = _test;
        var unescaped = new StringBuilder(escaped.Length);

        int currentIndex = 0;

        while (true)
        {
            var slashIndex = escaped.IndexOf('\\', currentIndex);

            if (-1 == slashIndex)
            {
                unescaped.Append(escaped, currentIndex, escaped.Length - currentIndex);
                break;
            }

            unescaped.Append(escaped, currentIndex, slashIndex - currentIndex);
            currentIndex = slashIndex;

            var nextChar = escaped[slashIndex + 1];

            switch (nextChar)
            {
                case '\\':
                    unescaped.Append('\\');
                    break;

                case 'n':
                    unescaped.Append('\n');
                    break;

                default:
                    throw new InvalidOperationException("nextChar=" + nextChar);
            }

            currentIndex++;
            currentIndex++;
        }

        return unescaped.ToString();
    }
}

internal class Solution
{
    private static void Main(string[] args)
    {
        BenchmarkRunner.Run<Test>();
        Console.ReadLine();
    }
}
Re[5]: 5 вариантов unescape - угадайте какой самый быстрый (
От: _NN_ www.nemerleweb.com
Дата: 30.11.19 06:50
Оценка:
Здравствуйте, Shmj, Вы писали:

Использование Stopwatch не всегда правильно отражает действительность.
Проверяйте через Benchmark.NET
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[5]: 5 вариантов unescape - угадайте какой самый быстрый (
От: Ночной Смотрящий Россия  
Дата: 30.11.19 07:27
Оценка:
Здравствуйте, Shmj, Вы писали:

S>Странно, у меня другие результаты:


Кто бы сомневался. Если человек альтернативно талантлив, он талантлив во всем, включая программирование.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[6]: 5 вариантов unescape - угадайте какой самый быстрый (
От: Mystic Artifact  
Дата: 30.11.19 09:09
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>Использование Stopwatch не всегда правильно отражает действительность.

Что же он по вашему отражает?
Re[7]: 5 вариантов unescape - угадайте какой самый быстрый (
От: _NN_ www.nemerleweb.com
Дата: 30.11.19 11:43
Оценка:
Здравствуйте, Mystic Artifact, Вы писали:

MA>Здравствуйте, _NN_, Вы писали:


_NN>>Использование Stopwatch не всегда правильно отражает действительность.

MA> Что же он по вашему отражает?

Нужно учитывать сборку мусора, прогрев JIT компилятора.
Не забыть запускать релизную сборку правильно. Бывает, что запускают под отладчиком.

https://github.com/dotnet/BenchmarkDotNet
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[8]: 5 вариантов unescape - угадайте какой самый быстрый (
От: Mystic Artifact  
Дата: 30.11.19 21:06
Оценка: +1 -1
Здравствуйте, _NN_, Вы писали:

MA>> Что же он по вашему отражает?

_NN>Нужно учитывать сборку мусора, прогрев JIT компилятора.
_NN>Не забыть запускать релизную сборку правильно. Бывает, что запускают под отладчиком.
Конечно это всё нужно учитывать и делать правильно. Есть еще масса других вещей которые учесть невозможно или трудно, наподобии влияния ОС, фоновых задач, спрогнозировать степень деградации красивых цифр полученных с помощью любых утилит, навроде бенчмаркдотнет, при увеличении нагрузки/конкуренции.

Вместе с тем, время выполнения — очень легко доступный показатель (и его производные) и к счастью именно оно отражает субьективную реальность, при чем его сильная сторона и она же ахилесова пята, что в него уже включены все затраты: на старт процесса, на JIT, на GC, кол-во перемолотой памяти.

PS: Не нравится Stopwatch — можно взять стрелочный секундомер.
Отредактировано 30.11.2019 21:07 Mystic Artifact . Предыдущая версия .
Re[6]: 5 вариантов unescape - угадайте какой самый быстрый (
От: Shmj Ниоткуда  
Дата: 01.12.19 01:07
Оценка: :))
Здравствуйте, _NN_, Вы писали:

_NN>Использование Stopwatch не всегда правильно отражает действительность.

_NN>Проверяйте через Benchmark.NET

На самом деле я затупил — проверял в режиме Debug.
Re: 5 вариантов unescape - угадайте какой самый быстрый (неожида
От: L_G Россия  
Дата: 01.12.19 09:19
Оценка: 1 (1)
Shmj,
Упростил вариант adetkov. Есть ускорение.
    private static string Test7(string escaped)
    {
        char[] array = escaped.ToCharArray();

        int i = 0, j = 0;
        while (j < array.Length) // странно, но чуть быстрее, чем for
        {
            if (array[j] == '\\')
            {
                j++;
                if (j >= array.Length) // увы, но для полной корректности придется проверить
                    break;
                if (array[j] == 'n')
                    array[i] = '\n';
                else
                    array[i] = array[j];
            }
            else
                array[i] = array[j];
            i++; j++;
        }
        return new string(array, 0, i);
    }
Каша в голове — пища для ума (с)
Re[7]: 5 вариантов unescape - угадайте какой самый быстрый (
От: Ночной Смотрящий Россия  
Дата: 01.12.19 09:28
Оценка:
Здравствуйте, Shmj, Вы писали:

S>На самом деле я затупил — проверял в режиме Debug.


Причем, похоже, не первый раз — .Net Core в действии...
Автор: Shmj
Дата: 28.11.19
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re: 5 вариантов unescape - угадайте какой самый быстрый (нео
От: Sinclair Россия https://github.com/evilguest/
Дата: 06.12.19 08:30
Оценка: 72 (7)
Здравствуйте, Shmj, Вы писали:

Неожиданно, самый быстрый вариант выглядит так:
  AVXUnescape()
        [Benchmark]
        public unsafe string AVXUnescape()
        {
            if (!Avx2.IsSupported)
                return UnsafeCharArrayScan();        // fallback для процессоров без AVX2. По-хорошему, надо деградировать постепенно.
            var slash = stackalloc ushort[1] { '\\' };
            var nchar = stackalloc ushort[1] { 'n' };
            var slashes = Avx2.BroadcastScalarToVector256(slash);
            var nchars = Avx2.BroadcastScalarToVector256(nchar);

            var result = new char[_escaped.Length];
            var escaped = _escaped.AsSpan(); 
            fixed (char* s = escaped, t = result)
            {
                var src = (ushort*)s;
                var trg = (ushort*)t;
                var i = 0;
                var j = 0;
                var trailingSlash = false;
                var k = (int)((((UIntPtr)(void*)src).ToUInt64() >> 1) & 0xF);  // вычисляем смещение src 

                while (i + 16 <= escaped.Length)  // проверяем, что остаток больше либо равен длине векторного регистра
                {
                    while (((k + i) & 0xf) > 0)   // медленный поиск для выравнивания по 32-byte boundary
                    {
                        if (trailingSlash)
                            switch(s[i])
                            {
                                case 'n':  // \n
                                    t[j - 1] = '\n';
                                    goto case '\\';
                                case '\\': // double-slash
                                  i++;
                                  trailingSlash = false;
                                  continue;
                            }

                        trailingSlash = s[i] == '\\';
                        t[j++] = s[i++];
                    }
                    var current = Avx2.LoadAlignedVector256(src + i); // чтение с выравниванием выигрывает примерно 30% времени от обычного
                    var slashbits = (uint)Avx2.MoveMask(Avx2.CompareEqual(current, slashes).AsByte());
                    var ncharbits = (uint)(Avx2.MoveMask(Avx2.CompareEqual(current, nchars).AsByte()));

                    if (slashbits == 0 && ncharbits == 0) // no slashes, no n chars
                    {
                        Avx2.Store(trg + j, current);
                        i += 16; j += 16;
                        continue;
                    }

                    if (trailingSlash)
                    {
                        if ((slashbits & 1) > 0) // starting slash after trailing slash
                        {
                            i++;
                            trailingSlash = false;
                            continue;
                        }
                        if ((ncharbits & 1) > 0)
                        {
                            i++;
                            trailingSlash = false;
                            trg[j - 1] = '\n';
                            continue;
                        }
                    }

                    var doubleslashbits = (slashbits >> 2) & slashbits;
                    var slashncharbits = (ncharbits >> 2) & slashbits;

                    var snpos = (int)Bmi1.TrailingZeroCount(slashncharbits) / 2;

                    if (doubleslashbits == 0)
                    {
                        if (slashncharbits == 0)  // no \\, nor \n - plain copy
                        {
                            Avx2.Store(trg + j, current);
                            i += 16; j += 16;
                            trailingSlash = (slashbits & 0xF0000000) == 1;
                        }
                        else
                        {
                            var n = snpos;
                            while (n >= 4)
                            {
                                var ls = (ulong*)(src + i);
                                var lt = (ulong*)(trg + j);
                                *lt = *ls;
                                n -= 4;
                                i += 4; j += 4;
                            }
                            if (n >= 2)
                            {
                                var qs = (uint*)(src + i);
                                var qt = (uint*)(trg + j);
                                *qt = *qs;
                                n -= 2;
                                i += 2; j += 2;
                            }
                            if (n > 0)
                            {
                                trg[j] = src[i];
                                j++; i++;
                            }
                            trg[j++] = '\n';
                            i += 2;
                        }
                        continue;
                    }

                    var dspos = (int)Bmi1.TrailingZeroCount(doubleslashbits) / 2;
                    if (slashncharbits != 0 && snpos < dspos)
                    {
                        var n = snpos;
                        while (n >= 4)
                        {
                            var ls = (ulong*)(src + i);
                            var lt = (ulong*)(trg + j);
                            *lt = *ls;
                            n -= 4;
                            i += 4; j += 4;
                        }
                        if (n >= 2)
                        {
                            var qs = (uint*)(src + i);
                            var qt = (uint*)(trg + j);
                            *qt = *qs;
                            n -= 2;
                            i += 2; j += 2;
                        }
                        if (n > 0)
                        {
                            trg[j] = src[i];
                            j++; i++;
                        }
                        trg[j++] = '\n';
                        i += 2;
                    }
                    else
                    {
                        var n = dspos + 1; 
                        while (n >= 4)
                        {
                            var ls = (ulong*)(src + i);
                            var lt = (ulong*)(trg + j);
                            *lt = *ls;
                            n -= 4;
                            i += 4; j += 4;
                        }
                        if (n >= 2)
                        {
                            var qs = (uint*)(src + i);
                            var qt = (uint*)(trg + j);
                            *qt = *qs;
                            n -= 2;
                            i += 2; j += 2;
                        }
                        if (n > 0)
                        {
                            trg[j] = src[i];
                            j++; i++;
                        }
                        i++;
                    }
                }

                // handle the remainder
                while (i < escaped.Length)
                {
                    if (trailingSlash)
                        switch (s[i])
                        {
                            case 'n':  // \n
                                t[j - 1] = '\n';
                                goto case '\\';
                            case '\\': // double-slash
                                i++;
                                trailingSlash = false;
                                continue;
                        }

                    trailingSlash = s[i] == '\\';
                    t[j++] = s[i++];
                }
                return new string(result, 0, j);
            }
        }


Результаты моего лэптопа — https://files.rsdn.org/5743/TestUnescape.UnescapeTests-report.html.
За единицу взят предыдущий победитель: https://rsdn.org/forum/dotnet/7601511.1
Автор: L_G
Дата: 01.12.19

N = количество повторов (в оригинале — 2000000, я сократил).
Scale — это коэффициент уменьшения плотности спецсимволов. В оригинале их напихано очень часто; я проверял, как зависит производительность от их частоты. В пределе, при очень больших Scale, в строке вообще не встречается ни \ ни n, и алгоритм вырождается в тупое копирование строки. При scale = 1 частоты совпадают с исходными.
Вот, собственно, код инициализации:
  UnescapeTests()
        public UnescapeTests()
        {
            var sb = new StringBuilder(N * 7);

            sb.Append("start:");

            for (int i = 0; i < N; i++)
            {
                if (_r.Next(1, 3 * Scale) == 1)
                    sb.Append("\\");

                if (_r.Next(1, 3 * Scale) == 1)
                    sb.Append("\\");

                if (_r.Next(1, 3 * Scale) == 1)
                    sb.Append("\\n");

                if (_r.Next(1, 4 * Scale) == 1)
                    sb.Append("\n");

                sb.Append("word");
            }

            _escaped = sb.ToString()
                         .Replace("\\", "\\\\")
                         .Replace("\n", "\\n");
        }

Понятно, что по соображениям воспроизводимости Random инициализируется константой. (42).
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Отредактировано 06.12.2019 8:31 Sinclair . Предыдущая версия .
Re[2]: 5 вариантов unescape - угадайте какой самый быстрый (нео
От: Shmj Ниоткуда  
Дата: 06.12.19 09:18
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Неожиданно, самый быстрый вариант выглядит так:


Это вы взяли готовый код для подобной задачи или писали с нуля для спорт. интереса?
Re[3]: 5 вариантов unescape - угадайте какой самый быстрый (нео
От: Sinclair Россия https://github.com/evilguest/
Дата: 06.12.19 09:50
Оценка:
Здравствуйте, Shmj, Вы писали:
S>Это вы взяли готовый код для подобной задачи или писали с нуля для спорт. интереса?
C нуля для спорт. интереса.
Для продакшна такой код не готов по двум причинам:
1. Нет плавной деградации. Помимо AVX2, есть ещё AVX, SSE42, SSE4 и прочее.
2. Плохо, нечитаемо написан. Скажем, доработать его для unescaping-а символов типа \t будет ненамного легче, чем переписать с нуля.
Тут надо быть очень аккуратным — один лишний вызов изнутри цикла страшно портит перформанс. Я пробовал поиграть с AggressiveInlining — не очень успешно.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: 5 вариантов unescape - угадайте какой самый быстрый (нео
От: Shmj Ниоткуда  
Дата: 06.12.19 11:09
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>>Это вы взяли готовый код для подобной задачи или писали с нуля для спорт. интереса?

S>C нуля для спорт. интереса.

Похоже как дети в песочнице спорили кто дальше плюнет, пока не пришел взрослый дядя и не продемонстрировал свои возможности.
Re[5]: 5 вариантов unescape - угадайте какой самый быстрый (
От: Sinclair Россия https://github.com/evilguest/
Дата: 06.12.19 11:25
Оценка:
Здравствуйте, Shmj, Вы писали:
S>Похоже как дети в песочнице спорили кто дальше плюнет, пока не пришел взрослый дядя и не продемонстрировал свои возможности.
Да ну что вы право. Это как раз игры в песочнице. Взрослые дяди занимаются всякими декодированиями из base64, которые работают на AVX512 быстрее, чем копирование памяти. И не делают детских ошибок — в коде было минимум 2 баги, которые просто не проявляются на тестовых данных.

Вот, после некоторого дополнительного тупления более простой и на ~10% более быстрый код, основанный на том, что даже при обнаружении \\ или \n у нас данные уже лежат в регистре current, поэтому мучительные копирования память-память нафиг не нужны:
  AVXUnescape2
        public unsafe string AVXUnescape2()
        {
            if (!Avx2.IsSupported)
                return UnsafeCharArrayScan();
            var slash = stackalloc ushort[1] { '\\' };
            var nchar = stackalloc ushort[1] { 'n' };
            var slashes = BroadcastScalarToVector256(slash);
            var nchars = BroadcastScalarToVector256(nchar);

            var result = new char[_escaped.Length];
            var escaped = _escaped.AsSpan();
            fixed (char* s = escaped, t = result)
            {
                var src = (ushort*)s;
                var trg = (ushort*)t;
                var i = 0;
                var j = 0;
                var trailingSlash = false;
                var k = (int)((((UIntPtr)(void*)src).ToUInt64() >> 1) & 0xF);

                while (i + 16 <= escaped.Length)
                {
                    while (((k + i) & 0xf) > 0)
                    {
                        if (trailingSlash)
                            switch (s[i])
                            {
                                case 'n':  // \n
                                    t[j - 1] = '\n';
                                    goto case '\\';
                                case '\\': // double-slash
                                    i++;
                                    trailingSlash = false;
                                    continue;
                            }

                        trailingSlash = s[i] == '\\';
                        t[j++] = s[i++];
                    }
                    var current = Avx2.LoadAlignedVector256(src + i);
                    var slashbits = (uint)Avx2.MoveMask(Avx2.CompareEqual(current, slashes).AsByte());
                    var ncharbits = (uint)Avx2.MoveMask(Avx2.CompareEqual(current, nchars).AsByte());

                    Avx2.Store(trg + j, current); // всегда пишем 16 символов пачкой; от результатов поиска зависят только инкременты i и j, и запись \n.

                    if (slashbits == 0 && ncharbits == 0) // no slashes, no Ns.
                    {
                        i += 16; j += 16;
            trailingSlash = false;
                        continue;
                    }

                    if (trailingSlash)
                    {
                        if ((slashbits & 1) > 0) // starting slash after trailing slash
                        {
                            i++;
                            trailingSlash = false;
                            continue;
                        }
                        if ((ncharbits & 1) > 0)
                        {
                            i++;
                            trailingSlash = false;
                            trg[j - 1] = '\n';
                            continue;
                        }
                    }

                    var doubleslashbits = (slashbits >> 2) & slashbits;
                    var slashncharbits = (ncharbits >> 2) & slashbits;

                    var snpos = (int)Bmi1.TrailingZeroCount(slashncharbits) / 2;

                    if (doubleslashbits == 0)
                    {
                        if (slashncharbits == 0)
                        {
                            i += 16; j += 16;
                            trailingSlash = (slashbits & 0x80000000) == 1;
                        }
                        else
                        {
                            j += snpos;
                            trg[j++] = '\n';
                            i += snpos + 1;
                            trailingSlash = false;
                        }
                        continue;
                    }

                    var dspos = (int)Bmi1.TrailingZeroCount(doubleslashbits) / 2;
                    if (slashncharbits != 0 && snpos < dspos)
                    {
                        j += snpos;
                        trg[j++] = '\n';
                        i += snpos + 1;
                        trailingSlash = false;
                    }
                    else
                    {
                        j += dspos;
                        i += dspos + 1;
                        trailingSlash = false;
                    }
                }

                // handle the remainder
                while (i < escaped.Length)
                {
                    if (trailingSlash)
                        switch (s[i])
                        {
                            case 'n':  // \n
                                t[j - 1] = '\n';
                                goto case '\\';
                            case '\\': // double-slash
                                i++;
                                trailingSlash = false;
                                continue;
                        }

                    trailingSlash = s[i] == '\\';
                    t[j++] = s[i++];
                }
                return new string(result, 0, j);
            }
        }
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Отредактировано 06.12.2019 11:25 Sinclair . Предыдущая версия .
Re[2]: 5 вариантов unescape - угадайте какой самый быстрый (нео
От: Pavel Dvorkin Россия  
Дата: 07.12.19 11:39
Оценка:
Здравствуйте, Sinclair, Вы писали:

Можешь еще ускорить , перейдя на обработку в потоках по частям с последующим слиянием. Если памяти не жалко , конечно.
With best regards
Pavel Dvorkin
Re[3]: 5 вариантов unescape - угадайте какой самый быстрый (нео
От: Ночной Смотрящий Россия  
Дата: 07.12.19 11:41
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Можешь еще ускорить , перейдя на обработку в потоках по частям с последующим слиянием.


Для строк типичного размера ускорения не будет, можешь сам попробовать.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[4]: 5 вариантов unescape - угадайте какой самый быстрый (нео
От: Pavel Dvorkin Россия  
Дата: 07.12.19 14:16
Оценка:
Здравствуйте, Ночной Смотрящий, Вы писали:

НС>Для строк типичного размера ускорения не будет, можешь сам попробовать.


Для строк типичного размера (десятки, сотня символов) — что ни делай, все равно.
With best regards
Pavel Dvorkin
Re[3]: 5 вариантов unescape - угадайте какой самый быстрый (нео
От: Sinclair Россия https://github.com/evilguest/
Дата: 07.12.19 17:05
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Можешь еще ускорить , перейдя на обработку в потоках по частям с последующим слиянием. Если памяти не жалко , конечно.

Можно попробовать. Но у меня подозрение, что я и так упираюсь в memory bandwidth. Если это так, то доп. потоки только замедлят.
Вообще, моя SIMD версия хуже обычного по-словного unsafe при больших плотностях заменяемых символов и при вылете за L1 кэш.
Результат, который я опубликовал — врушный, я там неправильно инициализировал тесты.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[5]: 5 вариантов unescape - угадайте какой самый быстрый (нео
От: Ночной Смотрящий Россия  
Дата: 07.12.19 20:19
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Для строк типичного размера (десятки, сотня символов) — что ни делай, все равно.


Тут топик наглядно демонстрирует, что это не так.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[3]: 5 вариантов unescape - угадайте какой самый быстрый (нео
От: PM  
Дата: 07.12.19 20:57
Оценка: +1
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Можешь еще ускорить , перейдя на обработку в потоках по частям с последующим слиянием. Если памяти не жалко , конечно.


Насколько я помню, многопоточность и SSE вместе нормально не работали из-за throttling. С AVX, похоже, тоже есть трудности:
https://lemire.me/blog/2018/04/19/by-how-much-does-avx-512-slow-down-your-cpu-a-first-experiment/

Intel’s latest processors have advanced instructions (AVX-512) that may cause the core, or maybe the rest of the CPU to run slower because of how much power they use.

Re[9]: 5 вариантов unescape - угадайте какой самый быстрый (
От: Mystic Artifact  
Дата: 13.12.19 19:46
Оценка: +1 -1
Здравствуйте, Mystic Artifact, Вы писали:

@Sinclair, а с чем не согласен?

Давайте измерять например процент branch prediction hit/miss, показателей вагон. Всё доступно. Но они не покажут ничего интересного по сути.

А советовать людям пользоваться говнофреймворком — я считаю неправильно. BenchmarkDotNet — поделка, которая выстрелила абсолютно незаслуженно. Я возбудился в этом топике потому что, вместо принятия объективной реальности и измерения секундомером, началась старая песня про то, что у вас секундомер неправильный и подсовывают какую-то ахинею, в виде другого секундомера, которая работает *только* для хэллоуворд. При этом, акцента на том, что оба секундомера исправны, нужно только ими правильно пользоваться — не делалось.

Так что нет. Гори огнем бенчмаркдотнет. Он в 99% нафиг не упал.

Более того, ТС это и показал.
Re[4]: 5 вариантов unescape - угадайте какой самый быстрый (нео
От: Mystic Artifact  
Дата: 13.12.19 19:54
Оценка: +1
Здравствуйте, PM, Вы писали:

Там вроде бы три режима энергопотребления, и активное использование этой порнографии приводит к тому, что приводит. Это, в принципе, абсолютно нормально, но только если у тебя не 32+ ядра которые должны быть равномерно загружены. Все компиляторы, может быть кроме clang (не уверен), весьма сильно поумерили свой пыл в использовании инструкций о которых их не просят.
Re[2]: 5 вариантов unescape - угадайте какой самый быстрый (нео
От: AlexRK  
Дата: 13.12.19 21:08
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Неожиданно, самый быстрый вариант выглядит так:

S>[cut=AVXUnescape()]

Респектую. Я бы такое не написал. Слишком много низкоуровневой магии для меня.
Re[3]: 5 вариантов unescape - угадайте какой самый быстрый (нео
От: Sinclair Россия https://github.com/evilguest/
Дата: 14.12.19 05:36
Оценка:
Здравствуйте, AlexRK, Вы писали:

ARK>Респектую. Я бы такое не написал. Слишком много низкоуровневой магии для меня.

Для меня — тоже. В этом коде минимум три бага
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: 5 вариантов unescape - угадайте какой самый быстрый (нео
От: Shmj Ниоткуда  
Дата: 14.12.19 22:26
Оценка:
Здравствуйте, Sinclair, Вы писали:

ARK>>Респектую. Я бы такое не написал. Слишком много низкоуровневой магии для меня.

S>Для меня — тоже. В этом коде минимум три бага

Придирки — ясно же что код тут писался на скорую руку.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.