Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, Sharowarsheg, Вы писали:
S>>Она что у тебя там, всего один раз выполняется?
S>Один раз, но строка достаточно длинная.
Нет, нужно в тыщу раз больше, по крайней мере. Кроме того, это разные режимы — один раз и очень длинная строка, или много раз маленькие строки. В любом случае, самый быстрый будет тот, в котором больше всего for int i=0.... и меньше всего всяких прогрессивных LINQ и проча. Не исключено, что еще быстрее будет переделать строку в массив слов Uint16 и с ними работать.
Здравствуйте, 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); // 183var 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);
}
}
}
Здравствуйте, Mystic Artifact, Вы писали:
MA>Здравствуйте, _NN_, Вы писали:
_NN>>Использование Stopwatch не всегда правильно отражает действительность. MA> Что же он по вашему отражает?
Нужно учитывать сборку мусора, прогрев JIT компилятора.
Не забыть запускать релизную сборку правильно. Бывает, что запускают под отладчиком.
Здравствуйте, _NN_, Вы писали:
MA>> Что же он по вашему отражает? _NN>Нужно учитывать сборку мусора, прогрев JIT компилятора. _NN>Не забыть запускать релизную сборку правильно. Бывает, что запускают под отладчиком.
Конечно это всё нужно учитывать и делать правильно. Есть еще масса других вещей которые учесть невозможно или трудно, наподобии влияния ОС, фоновых задач, спрогнозировать степень деградации красивых цифр полученных с помощью любых утилит, навроде бенчмаркдотнет, при увеличении нагрузки/конкуренции.
Вместе с тем, время выполнения — очень легко доступный показатель (и его производные) и к счастью именно оно отражает субьективную реальность, при чем его сильная сторона и она же ахилесова пята, что в него уже включены все затраты: на старт процесса, на JIT, на GC, кол-во перемолотой памяти.
PS: Не нравится Stopwatch — можно взять стрелочный секундомер.
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).
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Shmj, Вы писали: S>Это вы взяли готовый код для подобной задачи или писали с нуля для спорт. интереса?
C нуля для спорт. интереса.
Для продакшна такой код не готов по двум причинам:
1. Нет плавной деградации. Помимо AVX2, есть ещё AVX, SSE42, SSE4 и прочее.
2. Плохо, нечитаемо написан. Скажем, доработать его для unescaping-а символов типа \t будет ненамного легче, чем переписать с нуля.
Тут надо быть очень аккуратным — один лишний вызов изнутри цикла страшно портит перформанс. Я пробовал поиграть с AggressiveInlining — не очень успешно.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: 5 вариантов unescape - угадайте какой самый быстрый (нео
Здравствуйте, Sinclair, Вы писали:
S>>Это вы взяли готовый код для подобной задачи или писали с нуля для спорт. интереса? S>C нуля для спорт. интереса.
Похоже как дети в песочнице спорили кто дальше плюнет, пока не пришел взрослый дядя и не продемонстрировал свои возможности.
Re[5]: 5 вариантов unescape - угадайте какой самый быстрый (
Здравствуйте, 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 remainderwhile (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);
}
}
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Можешь еще ускорить , перейдя на обработку в потоках по частям с последующим слиянием. Если памяти не жалко , конечно.
Можно попробовать. Но у меня подозрение, что я и так упираюсь в memory bandwidth. Если это так, то доп. потоки только замедлят.
Вообще, моя SIMD версия хуже обычного по-словного unsafe при больших плотностях заменяемых символов и при вылете за L1 кэш.
Результат, который я опубликовал — врушный, я там неправильно инициализировал тесты.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[5]: 5 вариантов unescape - угадайте какой самый быстрый (нео
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Можешь еще ускорить , перейдя на обработку в потоках по частям с последующим слиянием. Если памяти не жалко , конечно.
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 - угадайте какой самый быстрый (
Давайте измерять например процент branch prediction hit/miss, показателей вагон. Всё доступно. Но они не покажут ничего интересного по сути.
А советовать людям пользоваться говнофреймворком — я считаю неправильно. BenchmarkDotNet — поделка, которая выстрелила абсолютно незаслуженно. Я возбудился в этом топике потому что, вместо принятия объективной реальности и измерения секундомером, началась старая песня про то, что у вас секундомер неправильный и подсовывают какую-то ахинею, в виде другого секундомера, которая работает *только* для хэллоуворд. При этом, акцента на том, что оба секундомера исправны, нужно только ими правильно пользоваться — не делалось.
Так что нет. Гори огнем бенчмаркдотнет. Он в 99% нафиг не упал.
Более того, ТС это и показал.
Re[4]: 5 вариантов unescape - угадайте какой самый быстрый (нео
Там вроде бы три режима энергопотребления, и активное использование этой порнографии приводит к тому, что приводит. Это, в принципе, абсолютно нормально, но только если у тебя не 32+ ядра которые должны быть равномерно загружены. Все компиляторы, может быть кроме clang (не уверен), весьма сильно поумерили свой пыл в использовании инструкций о которых их не просят.
Re[2]: 5 вариантов unescape - угадайте какой самый быстрый (нео
Здравствуйте, AlexRK, Вы писали:
ARK>Респектую. Я бы такое не написал. Слишком много низкоуровневой магии для меня.
Для меня — тоже. В этом коде минимум три бага
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: 5 вариантов unescape - угадайте какой самый быстрый (нео
Здравствуйте, Sinclair, Вы писали:
ARK>>Респектую. Я бы такое не написал. Слишком много низкоуровневой магии для меня. S>Для меня — тоже. В этом коде минимум три бага
Придирки — ясно же что код тут писался на скорую руку.