Mono.SIMD на практике

Давно хотел посмотреть, как на деле обстоят дела с потоковой обработкой данных в .NET. И вот сегодня представился случай.

Код: недописанный класс, реализующий MD5. Использовался только один оператор для первого раунда (то, что в референсной реализации называется FF()).

using System;
using CryptoLib.Hashing;
using Mono.Simd;

namespace Test
{
    internal class Program
    {
        private static void Main()
        {
            var md5 = new Md5();
            const uint a = 1;
            const uint b = 2;
            const uint c = 3;
            const uint d = 4;
            var s = new Vector4ui(a, b, c, d);
            var x = new uint[16];
            uint r1 = md5.Op1(x, a, b, c, d, 0, 12, 0);
            Vector4ui r2 = md5.Op1(x, s, 0, 12, 0);

            const uint cycles = 100000000;
            DateTime st = DateTime.Now;
            for (uint i = 0; i < cycles; i++)
                r1 = md5.Op1(x, r1, b, c, d, 0, 12, 0);
            DateTime e1 = DateTime.Now;
            for (uint i = 0; i < cycles; i++)
                r2 = md5.Op1(x, r2, 0, 12, 0);
            DateTime e2 = DateTime.Now;

            int bits = IntPtr.Size*8;
            int ms1 = (e1 - st).Milliseconds;
            int ms2 = (e2 - e1).Milliseconds;

            Console.WriteLine("Running {0}-bit code.", bits);
            Console.WriteLine("Normal: {0}ms", ms1);
            Console.WriteLine("SIMD: {0}ms", ms2);
        }
    }
}

Реализация Md5.Op1() вовсе не замысловата:

public uint Op1(uint[] x,
                uint a, uint b, uint c, uint d,
                byte k, byte s, byte i)
{
    return b + (a + F(b, c, d) + x[k] + T[i]).Rol(s);
}

public Vector4ui Op1(uint[] x, Vector4ui state, byte k, byte s, byte i)
{
    state.X = state.Y + (state.X + F(state.Y, state.Z, state.W) + x[k] + T[i]).Rol(s);
    return state;
}

Среда выполнения:

  • 64-битная Windows 7 RC (build 7100);
  • Core2 Quad Q6600 (поддержка инструкций до SSSE3);
  • .NET Framework CLR v2.0.50727;
  • Mono 2.4 (под Windows есть только 32-битный CLR).

Результаты по трём запускам релизного кода:

Normal (ms)

SIMD (ms)

Runtime

Bitness

331,33

11,33

Mono+

32

320,33

131,33

Mono-

32

857,33

768,67

MS .NET

32

853,67

774,00

MS .NET

64

  • Mono+ – с включенной оптимизацией (-O=simd);
  • Mono- – с выключенной оптимизацией (-O=-simd).

Comments

6 комментариев на ««Mono.SIMD на практике»»

  1. Очень круто! Спасибо! Жаль только, что Mono в целом тормозная штука 🙂

  2. Я бы не сказал, что Mono работает медленней майкросовтовского JIT, просто они довольно сильно отличаются и в разных сценариях могут вести себя лучше или хуже.

    По поводу SIMD, на самом деле, это уже менее актуально, особенно с учётом релиза .NET 4.5, где майкросовтовский JIT начал требовать наличия SSE2 для своей работы.

  3. Аватар пользователя Vasily Pupenkovich
    Vasily Pupenkovich

    покажите хоть 1 инструкцию simd в вашем коде, не вижу.
    например в этой строке вобще нет ни 1 simd инструкции, манипуляция отдельными компонентами state

    state.X = state.Y + (state.X + F(state.Y, state.Z, state.W) + x[k] + T[i]).Rol(s);

  4. В .net вообще нет возможности кодогенерации машинного кода, всем этим занимается JIT. Mono.SIMD был первой попыткой в этом направлении: при использовании структур из этого неймспейса JIT мог автоматически задействовать подходящие инструкции.

    Это пост за 2009 год, тогда SIMD просто не использовался в кодогенерации и простое использование типов, даже когда эффективно оно не использовалось, позволяло задействовать новый кодогенератор.

    Майкрософтовский RyuJIT умеет кое-что автоматом конвертировать, но оптимальный результат по-прежнему будет только при использовании специализированных типов из System.Numerics.Vestors.

  5. Аватар пользователя Vasily Pupenkovich
    Vasily Pupenkovich

    я сейчас в simd в mono/unity возился, удалось кое-что потестить, так вот там Vector4f из Mono.Simd надо юзать и конструировать его перед каждыми операциями в духе
    Vector4f v1 = new Vector4f(1f, 2f, 3f, 4f)
    Vector4f v2 = new Vector4f(5f, 6f, 7f, 8f)
    Vector4f vRes = v1 * v2;
    что не приносит улучшений перфоманса, а местами даже наоборот изза кучи аллокаций. я попробовал даже переиспользовать Vector4f без new а задавать свойства X,Y,Z,W в цикле, так оно в mono крашится.

    il2cpp unity под android компилит обычные умножения для такого кода, без simd (какая заглушка в mono стоит для cpi неподдерживающих sse).

    в итоге не нашел ни 1 рабочего примера где бы показали как производительность поднять под Mono.Simd, а не уменьшить ее 🙂

  6. Mono уже давно поддерживает System.Numeric.Vectors, но оптимизации работают только при использовании LLVM-бэкенда и, возможно, только при указании ключа. Я хз чего там как в Unity, но есть подозрение что они сознательно используют более портабельную версию.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *