По идее эта штука может вырасти в динамическую игру, где в реальном времени плавно будет меняться сценарий, геймплей, графика и жанр, адаптируясь к игроку за счёт обратной связи через анализ эмоций. Да и грань между играми, приложениями и «мирами» имхо постепенно сотрётся.
В среднем я сижу где‑то на расстоянии 70см от центрального экрана, вполне себе получается читать текст, надписи в софте и всякие имена файлов в проводнике и сайты в 100% масштабе.
Всякие 3д‑максы, Visual Studio и After Effectsы для комфортной работы требуют очень много места на экране, а 200% масштаб его тотально сжирает
Я поиграл. Очередь быстро проходит, у меня была 52к человек, через 5 минут зашёл. По ощущениям — это и почти «тот самый BF3» только современный.
Мясо, темп и атмосфера больше всего напоминает бф3, нежели бф4. И, на удивление, он хорошо оптимизирован — играл 11 520×2160 было 30–40 фпс на ультрах.
Регуляция как раз штука удобная, а вот работа стоя, имхо — штука сомнительная. У самого подъёмный стол, но использую его для регуляции и как подъёмник, когда надо в системном блоке ковыряться. А стоя за ним не работаю.
Полноценная клава штука на любителя. Я вот себе поставил полноценную и обнаружил, что буквы теперь слишком сдвинуты влево, т.к. между мышкой и буквами этот нумпад, которым по факту я почти не пользуюсь (только телики поворачивать).
Нумпад нужен только тем кто его активно юзает, остальным он только место занимает. Имхо, лучшее решение — отдельный нумпад СПРАВА от мыши.
Так и есть, просто люди еще не свыклись с мыслью, что монитор может быть размером с телик. И то, что многие современные телики (не все) можно использовать в качестве мониторов.
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овская реализация, судя по всему, написана примерно так же, но оказалась лучше заточена под Ваше железо, и хуже под моё.
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;
}
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, а какие‑нибудь хитрые трюки с масками
В одну систему ставить топовый игровой проц и самый лоуенд по графике мало кто будет. Нереалистичная пара проц+графика абсолютно. Исследование сферического коня в вакууме.
Задумка ИМХО в том, чтобы исключить влияние процессора на результаты тестов. То есть мы строим систему, где заведомо видеокарта будет бутылочным горлышком.
Ради интереса грузил Qwen3 на 480b параметров с квантизацией Q3. Поручил писать алгоритм изменения яркости в духе
void DoBrightness(Bitmap batman, float multipler)
Попросил всё люто‑бешено оптимизировать с unsafe и LUT таблицами, и чтобы большие битмапы через Parallel. Пахало 12 часов, справилось, честно говоря, на троечку. Видимо квантизация помешала. Модельки на 32b и 72b справились примерно так же, зато ответ выдавали через 10–30 сек.
По идее эта штука может вырасти в динамическую игру, где в реальном времени плавно будет меняться сценарий, геймплей, графика и жанр, адаптируясь к игроку за счёт обратной связи через анализ эмоций. Да и грань между играми, приложениями и «мирами» имхо постепенно сотрётся.
В среднем я сижу где‑то на расстоянии 70см от центрального экрана, вполне себе получается читать текст, надписи в софте и всякие имена файлов в проводнике и сайты в 100% масштабе.
Всякие 3д‑максы, Visual Studio и After Effectsы для комфортной работы требуют очень много места на экране, а 200% масштаб его тотально сжирает
Телевизоров 8K уже давно много в продаже, в том числе и 55 дюймов. Sony BRAVIA 8 K-55XR80 как пример.
Никто не мешает играть мышкой и клавиатурой сидя на мягком диване перед большим телевизором.
Я поиграл. Очередь быстро проходит, у меня была 52к человек, через 5 минут зашёл. По ощущениям — это и почти «тот самый BF3» только современный.
Мясо, темп и атмосфера больше всего напоминает бф3, нежели бф4. И, на удивление, он хорошо оптимизирован — играл 11 520×2160 было 30–40 фпс на ультрах.
Сглаживание включено, шрифты стандартные, масштаб 100%, и мне норм.
Если нужна чёткость, можно просто поставить масштаб 200% и сесть подальше, но тогда на одном экране будет места как на обычном FullHD мониторе.
Если не нужно 120Гц+, можно просто юзать 8K панели. Мне 120 Гц критично, ибо шутеры, а если только работать с текстом , то 8К 60 Гц вполне норм.
Регуляция как раз штука удобная, а вот работа стоя, имхо — штука сомнительная. У самого подъёмный стол, но использую его для регуляции и как подъёмник, когда надо в системном блоке ковыряться. А стоя за ним не работаю.
Полноценная клава штука на любителя. Я вот себе поставил полноценную и обнаружил, что буквы теперь слишком сдвинуты влево, т.к. между мышкой и буквами этот нумпад, которым по факту я почти не пользуюсь (только телики поворачивать).
Нумпад нужен только тем кто его активно юзает, остальным он только место занимает. Имхо, лучшее решение — отдельный нумпад СПРАВА от мыши.
Так и есть, просто люди еще не свыклись с мыслью, что монитор может быть размером с телик. И то, что многие современные телики (не все) можно использовать в качестве мониторов.
А думаете стационарный отключать не будут? :)
Есть вот такие подлокотники для стола:
На вкус и цвет. Мне вот 80 PPI норм.
Хм. У меня наоборот получается, что немного выигрывает:
Код
Предположу, что дело в особенностях реализации SIMD на конкретном железе, размерах кэша, скорости ОЗУ и прочих штуках, поэтому разные подходы дают разные результаты на разном железе. И Spanовская реализация, судя по всему, написана примерно так же, но оказалась лучше заточена под Ваше железо, и хуже под моё.
UPD: вариант, заточенный под мелкие строки:
Код
Для интереса попробовал на интринсиках (.NET9/Release):
Строки в 12 символов за 1.1 — 1.7 нс
Строки в 256 миллионов символов — 10–11 мс
Использование AVX512 прироста не дало
Код бенчмарка
Дальше можно в зависимости от фактического размера данных затачиваться:
Под короткие строки — тогда выкидываем тушу с векторами и делаем ступенчатую обработку (по типу блока с Vector128, который под тушкой векторов), то есть сначала один раз без цикла проверяем 512, потом 256, потом 128 бит, потом анролл, потом обычный цикл
Под длинные строки — тогда можно перед тушкой с векторами поставить проверку по 1 цифре, которая подравняет указатель памяти до кратности размеру вектора, и далее, в тушке векторов, делать не Load, а LoadAligned, что немного быстрее. Ну и можно поиграться со способом проверки — не Clamp/Equals, а какие‑нибудь хитрые трюки с масками
Задумка ИМХО в том, чтобы исключить влияние процессора на результаты тестов. То есть мы строим систему, где заведомо видеокарта будет бутылочным горлышком.
Ради интереса грузил Qwen3 на 480b параметров с квантизацией Q3. Поручил писать алгоритм изменения яркости в духе
Попросил всё люто‑бешено оптимизировать с unsafe и LUT таблицами, и чтобы большие битмапы через Parallel. Пахало 12 часов, справилось, честно говоря, на троечку. Видимо квантизация помешала. Модельки на 32b и 72b справились примерно так же, зато ответ выдавали через 10–30 сек.
Выводов не будет, просто делюсь опытом.
LM Studio кстати тоже умеет поднимать сервер, даже несколько моделей параллельно можно грузить.
Что‑то многовато новостей про сокращения в важных инфраструктурных организациях там (Intel машет ручкой). Интересно, к чему это приведёт
AGI не для решения задач нужен, а для более масштабных и многовекторных целей