Обновить
297

Пользователь

136
Подписчики
Отправить сообщение

Так‑то это (Ecstatica) уникальная игра, где в движке в качестве примитивов используются не треугольники, как мы привыкли, а эллипсоиды. Но, как говорится, всё новое — это хорошо забытое старое — эллипсоиды нынче вернулись в виде 3D gaussian splatting.

Ещё была Rebel Moon Rising, там тоже что‑то подобное использовалось.

На GPU она считается. Они просто перестали поддерживать 32-битный PhysX, 64-битный остался.

Есть отдельный алгоритм для быстрого 1/x, он гораздо проще.

float fast_rep(float a)
{
  static_assert(sizeof(int) == sizeof(float), "не прокатило :(");
  
  int b = 0x7EF311C2 - *(int*)&a;
  return *(float*)&b;
}

И его, кстати, легко реализовать на simdах.

Что с инпутлагом?

И кстати я тут подумал — есть сейчас в играх DLSS и Frame Generation. По идее можно транслировать картинку с низким разрешением и FPS, и уже на стороне клиента делать апскейл и генерацию кадров (в том числе будущих). Понятно что это не обычный апскейл и надо будет доп. информацию транслировать из рендерера игрового движка, но всё таки почему бы и нет. Есть вероятность что это будет иметь плюсы по сравнению с обычной трансляцией.

Зависит от состава и от реальной способности камеры видеть UV излучение

Поковырял, погонял в реалтайме на видеокарте. Ну такое себе. В будущем, возможно, результат будет лучше.

По идее эта штука может вырасти в динамическую игру, где в реальном времени плавно будет меняться сценарий, геймплей, графика и жанр, адаптируясь к игроку за счёт обратной связи через анализ эмоций. Да и грань между играми, приложениями и «мирами» имхо постепенно сотрётся.

Как вы без зума видите эти мелкие шрифты?

В среднем я сижу где‑то на расстоянии 70см от центрального экрана, вполне себе получается читать текст, надписи в софте и всякие имена файлов в проводнике и сайты в 100% масштабе.

Всякие 3д‑максы, Visual Studio и After Effectsы для комфортной работы требуют очень много места на экране, а 200% масштаб его тотально сжирает

Телевизоров 8K уже давно много в продаже, в том числе и 55 дюймов. Sony BRAVIA 8 K-55XR80 как пример.

Сглаживание включено, шрифты стандартные, масштаб 100%, и мне норм.

Если нужна чёткость, можно просто поставить масштаб 200% и сесть подальше, но тогда на одном экране будет места как на обычном FullHD мониторе.

Если не нужно 120Гц+, можно просто юзать 8K панели. Мне 120 Гц критично, ибо шутеры, а если только работать с текстом , то 8К 60 Гц вполне норм.

Регуляция как раз штука удобная, а вот работа стоя, имхо — штука сомнительная. У самого подъёмный стол, но использую его для регуляции и как подъёмник, когда надо в системном блоке ковыряться. А стоя за ним не работаю.

Полноценная клава штука на любителя. Я вот себе поставил полноценную и обнаружил, что буквы теперь слишком сдвинуты влево, т.к. между мышкой и буквами этот нумпад, которым по факту я почти не пользуюсь (только телики поворачивать).

Нумпад нужен только тем кто его активно юзает, остальным он только место занимает. Имхо, лучшее решение — отдельный нумпад СПРАВА от мыши.

Так и есть, просто люди еще не свыклись с мыслью, что монитор может быть размером с телик. И то, что многие современные телики (не все) можно использовать в качестве мониторов.

А думаете стационарный отключать не будут? :)

Есть вот такие подлокотники для стола:

На вкус и цвет. Мне вот 80 PPI норм.

Хм. У меня наоборот получается, что немного выигрывает:

| Method                               | Mean              | Error           | StdDev          |
|------------------------------------- |------------------:|----------------:|----------------:|
| LEN_12_only_digits                   |          1.652 ns |       0.0026 ns |       0.0023 ns |
| LEN_12_NOT_only_digits               |          1.093 ns |       0.0019 ns |       0.0015 ns |
| LEN_256_000_000_only_digits          | 10,282,478.846 ns |  60,604.1205 ns |  50,607.1854 ns |
| LEN_256_000_000_NOT_only_digits      |  9,564,862.960 ns | 180,272.5571 ns | 185,126.6438 ns |
| SPAN_LEN_12_only_digits              |          1.656 ns |       0.0049 ns |       0.0046 ns |
| SPAN_LEN_12_NOT_only_digits          |          1.897 ns |       0.0014 ns |       0.0012 ns |
| SPAN_LEN_256_000_000_only_digits     | 10,340,051.146 ns | 181,664.4755 ns | 169,929.0632 ns |
| SPAN_LEN_256_000_000_NOT_only_digits |  9,199,522.098 ns |  90,575.2054 ns |  80,292.5604 ns |
Код
 public static bool IsAllDigitsSpan([NotNull] string str) =>
   str.AsSpan().IndexOfAnyExceptInRange('0', '9') == -1;
 public static bool IsAllDigits([NotNull] string str)
 {
     unsafe
     {
         fixed (char* begin = str)
         {
             char* ptr = begin;
             var charsInVector = Vector<ushort>.Count;
             if (str.Length >= charsInVector) //Если строка достаточно длинная для векторов
             {
                 //Вычисляем конец чтобы поместилось целое число векторов
                 char* endVector = begin + str.Length / charsInVector * charsInVector;

                 var min = new Vector<ushort>((ushort)'0'); //Вектор состоящий из '0'
                 var max = new Vector<ushort>((ushort)'9'); //Вектор состоящий из '9'

                 //Идея простая: берем пачки по N символов
                 //загоняем в диапазон от '0' до '9'
                 //и смотрим есть ли изменения - если есть, значит это были не цифры
                 while (ptr < endVector)
                 {
                     var v = Vector.Load((ushort*)ptr); //Загружаем 16 символов в регистр 
                     var vClamped = Vector.ClampNative(v, min, max); //Загоняем в диапазон от '0' до '9'

                     if (!Vector.EqualsAll(v, vClamped)) //Если что-то изменилось то там были не только цифры
                         return false;

                     ptr += charsInVector; //Идем дальше
                 }
             }
             char* end = begin + str.Length;

             //Если осталось хотя бы 8 символов - добиваем 128-битной инструкцией
             if (end - ptr >= Vector128<ushort>.Count)
             {
                 var min128 = Vector128.Create((ushort)'0');
                 var max128 = Vector128.Create((ushort)'9');
                 var v = Vector128.Load<ushort>((ushort*)ptr);
                 if (!Vector128.EqualsAll(Vector128.Clamp(v, min128, max128), v))
                     return false;
                 else
                     ptr += Vector128<ushort>.Count;
             }

             //Далее анролл по 4
             char* end4 = begin + (str.Length & ~3);
             while (ptr < end4)
             {
                 if (unchecked((uint)(*ptr - '0')) > 9u) return false; ptr++;
                 if (unchecked((uint)(*ptr - '0')) > 9u) return false; ptr++;
                 if (unchecked((uint)(*ptr - '0')) > 9u) return false; ptr++;
                 if (unchecked((uint)(*ptr - '0')) > 9u) return false; ptr++;
             }

             //Потом обычным циклом
             while (ptr < end)
             {
                 if (unchecked((uint)(*ptr - '0')) > 9u)
                     return false;
                 ptr++;
             }
         }
     }
     return true;
 }
   [Benchmark] public bool LEN_12_only_digits() => TurboIsDigitChecker.IsAllDigits(small_only_digits);
   [Benchmark] public bool LEN_12_NOT_only_digits() => TurboIsDigitChecker.IsAllDigits(small);
   [Benchmark] public bool LEN_256_000_000_only_digits() => TurboIsDigitChecker.IsAllDigits(big_only_digits);
   [Benchmark] public bool LEN_256_000_000_NOT_only_digits() => TurboIsDigitChecker.IsAllDigits(big);

   [Benchmark] public bool SPAN_LEN_12_only_digits() => TurboIsDigitChecker.IsAllDigitsSpan(small_only_digits);
   [Benchmark] public bool SPAN_LEN_12_NOT_only_digits() => TurboIsDigitChecker.IsAllDigitsSpan(small);
   [Benchmark] public bool SPAN_LEN_256_000_000_only_digits() => TurboIsDigitChecker.IsAllDigitsSpan(big_only_digits);
   [Benchmark] public bool SPAN_LEN_256_000_000_NOT_only_digits() => TurboIsDigitChecker.IsAllDigitsSpan(big);

Предположу, что дело в особенностях реализации SIMD на конкретном железе, размерах кэша, скорости ОЗУ и прочих штуках, поэтому разные подходы дают разные результаты на разном железе. И Spanовская реализация, судя по всему, написана примерно так же, но оказалась лучше заточена под Ваше железо, и хуже под моё.

UPD: вариант, заточенный под мелкие строки:

| Method                       | Mean      | Error     | StdDev    |
|----------------------------- |----------:|----------:|----------:|
| LEN_12_only_digits_SMALL     | 1.4944 ns | 0.0083 ns | 0.0077 ns |
| LEN_12_NOT_only_digits_SMALL | 0.9210 ns | 0.0088 ns | 0.0083 ns |
| LEN_12_only_digits_SPAN      | 1.7246 ns | 0.0053 ns | 0.0047 ns |
| LEN_12_NOT_only_digits_SPAN  | 1.8502 ns | 0.0058 ns | 0.0055 ns |
Код
   public static bool IsAllDigitsSpan([NotNull] string str) =>
            str.AsSpan().IndexOfAnyExceptInRange('0', '9') == -1;
   public static bool IsAllDigits_FOR_SMALL([NotNull] string str)
   {
       unsafe
       {
           fixed (char* begin = str)
           {
               char* ptr = begin;
               char* end = begin + str.Length;

               //8 символов
               if (end - ptr >= Vector128<ushort>.Count)
               {
                   var min128 = Vector128.Create((ushort)'0');
                   var max128 = Vector128.Create((ushort)'9');
                   var v = Vector128.Load<ushort>((ushort*)ptr);
                   if (!Vector128.EqualsAll(Vector128.Clamp(v, min128, max128), v))
                       return false;
                   else
                       ptr += Vector128<ushort>.Count;
               }

               //4 символа
               char* end4 = begin + (str.Length & ~3);
               if (ptr < end4)
               {
                   if (unchecked((uint)(*ptr - '0')) > 9u) return false; ptr++;
                   if (unchecked((uint)(*ptr - '0')) > 9u) return false; ptr++;
                   if (unchecked((uint)(*ptr - '0')) > 9u) return false; ptr++;
                   if (unchecked((uint)(*ptr - '0')) > 9u) return false; ptr++;
               }

               //Добиваем обычным циклом
               while (ptr < end)
               {
                   if (unchecked((uint)(*ptr - '0')) > 9u)
                       return false;
                   ptr++;
               }
           }
       }
       return true;
   }
  [Benchmark] public bool LEN_12_only_digits_SMALL() => TurboIsDigitChecker.IsAllDigits_FOR_SMALL(small_only_digits);
  [Benchmark] public bool LEN_12_NOT_only_digits_SMALL() => TurboIsDigitChecker.IsAllDigits_FOR_SMALL(small);
  [Benchmark] public bool LEN_12_only_digits_SPAN() => TurboIsDigitChecker.IsAllDigitsSpan(small_only_digits);
  [Benchmark] public bool LEN_12_NOT_only_digits_SPAN() => TurboIsDigitChecker.IsAllDigitsSpan(small);

Для интереса попробовал на интринсиках (.NET9/Release):

| Method                             | Mean              | Error          | StdDev         |
|----------------------------------- |------------------:|---------------:|---------------:|
| LEN_12_only_digits                 |          1.668 ns |      0.0088 ns |      0.0083 ns |
| LEN_12_NOT_only_digits             |          1.099 ns |      0.0062 ns |      0.0055 ns |
| LEN_256_000_000_only_digits        | 10,137,419.231 ns | 77,538.9046 ns | 64,748.4971 ns |
| LEN_256_000_000_NOT_only_digits    |  9,201,213.951 ns | 92,295.2075 ns | 81,817.2974 ns |
  • Строки в 12 символов за 1.1 — 1.7 нс

  • Строки в 256 миллионов символов — 10–11 мс

public static bool IsAllDigits([NotNull] string str)
{
    unsafe
    {
        fixed (char* begin = str)
        {
            char* ptr = begin;
            var charsInVector = Vector<ushort>.Count;
            if (str.Length >= charsInVector) //Если строка достаточно длинная для векторов
            {
                //Вычисляем конец чтобы поместилось целое число векторов
                char* endVector = begin + str.Length / charsInVector * charsInVector;

                var min = new Vector<ushort>((ushort)'0'); //Вектор состоящий из '0'
                var max = new Vector<ushort>((ushort)'9'); //Вектор состоящий из '9'

                //Идея простая: берем пачки по N символов
                //загоняем в диапазон от '0' до '9'
                //и смотрим есть ли изменения - если есть, значит это были не цифры
                while (ptr < endVector)
                {
                    var v = Vector.Load((ushort*)ptr); //Загружаем 16 символов в регистр 
                    var vClamped = Vector.ClampNative(v, min, max); //Загоняем в диапазон от '0' до '9'

                    if (!Vector.EqualsAll(v, vClamped)) //Если что-то изменилось то там были не только цифры
                        return false;

                    ptr += charsInVector; //Идем дальше
                }
            }
            char* end = begin + str.Length;

            //Если осталось хотя бы 8 символов - добиваем 128-битной инструкцией
            if (end - ptr >= Vector128<ushort>.Count)
            {
                var min128 = Vector128.Create((ushort)'0');
                var max128 = Vector128.Create((ushort)'9');
                var v = Vector128.Load<ushort>((ushort*)ptr);
                if (!Vector128.EqualsAll(Vector128.Clamp(v, min128, max128), v))
                    return false;
                else
                    ptr += Vector128<ushort>.Count;
            }
           
            //Далее анролл по 4
            char* end4 = begin + (str.Length & ~3);
            while (ptr < end4)
            {
                if (unchecked((uint)(*ptr - '0')) > 9u) return false; ptr++;
                if (unchecked((uint)(*ptr - '0')) > 9u) return false; ptr++;
                if (unchecked((uint)(*ptr - '0')) > 9u) return false; ptr++;
                if (unchecked((uint)(*ptr - '0')) > 9u) return false; ptr++;
            }

            //Потом обычным циклом
            while (ptr < end)
            {
                if (unchecked((uint)(*ptr - '0')) > 9u)
                    return false;
                ptr++;
            }
        }
    }
    return true;
}
Использование AVX512 прироста не дало
| Method                             | Mean              | Error          | StdDev         |
|----------------------------------- |------------------:|---------------:|---------------:|
| LEN_12_only_digits512              |          1.676 ns |      0.0078 ns |      0.0073 ns |
| LEN_12_NOT_only_digits512          |          1.101 ns |      0.0063 ns |      0.0059 ns |
| LEN_256_000_000_only_digits512     | 10,133,206.250 ns | 77,341.0857 ns | 68,560.8580 ns |
| LEN_256_000_000_NOT_only_digits512 |  9,085,707.091 ns | 53,953.1981 ns | 45,053.3640 ns |
  public static bool IsAllDigits512([NotNull] string str)
  {
      unsafe
      {
          fixed (char* begin = str)
          {
              char* ptr = begin;
              var charsInVector512 = Vector512<ushort>.Count;
              //Основная тушка с векторами
              if (str.Length >= charsInVector512) //Если строка достаточно длинная для векторов
              {
                  //Вычисляем конец чтобы поместилось целое число векторов
                  char* endVector512 = begin + str.Length / charsInVector512 * charsInVector512;

                  var min = Vector512.Create((ushort)'0'); //Вектор состоящий из '0'
                  var max = Vector512.Create((ushort)'9'); //Вектор состоящий из '9'

                  //Идея простая: берем пачки по N символов
                  //загоняем в диапазон от '0' до '9'
                  //и смотрим есть ли изменения - если есть, значит это были не цифры
                  while (ptr < endVector512)
                  {
                      var v = Vector512.Load((ushort*)ptr); //Загружаем 16 символов в регистр 
                      var vClamped = Vector512.ClampNative(v, min, max); //Загоняем в диапазон от '0' до '9'

                      if (!Vector512.EqualsAll(v, vClamped)) //Если что-то изменилось то там были не только цифры
                          return false;

                      ptr += charsInVector512; //Идем дальше
                  }
              }

              char* end = begin + str.Length;

              //Если осталось хотя бы 8 символов - добиваем 128-битной инструкцией
              if (end - ptr >= Vector128<ushort>.Count)
              {
                  var min128 = Vector128.Create((ushort)'0');
                  var max128 = Vector128.Create((ushort)'9');
                  var v = Vector128.Load<ushort>((ushort*)ptr);
                  if (!Vector128.EqualsAll(Vector128.Clamp(v, min128, max128), v))
                      return false;
                  else
                      ptr += Vector128<ushort>.Count;
              }

              //Далее анролл по 4
              char* end4 = begin + (str.Length & ~3);
              while (ptr < end4)
              {
                  if (unchecked((uint)(*ptr - '0')) > 9u) return false; ptr++;
                  if (unchecked((uint)(*ptr - '0')) > 9u) return false; ptr++;
                  if (unchecked((uint)(*ptr - '0')) > 9u) return false; ptr++;
                  if (unchecked((uint)(*ptr - '0')) > 9u) return false; ptr++;
              }

              //Потом обычным циклом
              while (ptr < end)
              {
                  if (unchecked((uint)(*ptr - '0')) > 9u)
                      return false;
                  ptr++;
              }
          }
      }
      return true;
  }
Код бенчмарка
 public class Benchmark
 {
     static int randomIndex = 0;
     static string generateBigRandomString(bool onlyDigits, int count)
     {
         if (count < 1024)
             throw new NotSupportedException();
         var rnd = new Random(Interlocked.Increment(ref randomIndex));
         var b = new StringBuilder(count);
         var beginNoDigits = count / 10 * 9;
         for (int i = 0; i < count; i++)
             b.Append((char)rnd.Next('0' + 0, '9' + 1));

         if (!onlyDigits)
         {
             for (int i = 0; i < 10; i++)
             {
                 var index = rnd.Next(beginNoDigits, count);
                 b[index] = (char)rnd.Next('a' + 0, 'z' + 1);
             }
         }
         return b.ToString();
     }
     string small_only_digits, small;
     string big_only_digits, big;
     [GlobalSetup]
     public void Setup()
     {
         small_only_digits = "123456732890";
         small = "123456732a90";
         big_only_digits = generateBigRandomString(true, 256_000_000);
         big = generateBigRandomString(false, 256_000_000);
     }

     [Benchmark] public bool LEN_12_only_digits() => TurboIsDigitChecker.IsAllDigits(small_only_digits);
     [Benchmark] public bool LEN_12_NOT_only_digits() => TurboIsDigitChecker.IsAllDigits(small);
     [Benchmark] public bool LEN_256_000_000_only_digits() => TurboIsDigitChecker.IsAllDigits(big_only_digits);
     [Benchmark] public bool LEN_256_000_000_NOT_only_digits() => TurboIsDigitChecker.IsAllDigits(big);

     [Benchmark] public bool LEN_12_only_digits512() => TurboIsDigitChecker.IsAllDigits512(small_only_digits);
     [Benchmark] public bool LEN_12_NOT_only_digits512() => TurboIsDigitChecker.IsAllDigits512(small);
     [Benchmark] public bool LEN_256_000_000_only_digits512() => TurboIsDigitChecker.IsAllDigits512(big_only_digits);
     [Benchmark] public bool LEN_256_000_000_NOT_only_digits512() => TurboIsDigitChecker.IsAllDigits512(big);
 }

Дальше можно в зависимости от фактического размера данных затачиваться:

  • Под короткие строки — тогда выкидываем тушу с векторами и делаем ступенчатую обработку (по типу блока с Vector128, который под тушкой векторов), то есть сначала один раз без цикла проверяем 512, потом 256, потом 128 бит, потом анролл, потом обычный цикл

  • Под длинные строки — тогда можно перед тушкой с векторами поставить проверку по 1 цифре, которая подравняет указатель памяти до кратности размеру вектора, и далее, в тушке векторов, делать не Load, а LoadAligned, что немного быстрее. Ну и можно поиграться со способом проверки — не Clamp/Equals, а какие‑нибудь хитрые трюки с масками

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность