В прошлой статье я разбирался с тем как работает GIL, а сегодня меня захватила идея узнать насколько дорого обходится этот GIL для кода, который исполняется у нас на бэкенде. Для этого я решил пропатчить MRI и добавить пару переменных, в которых буду засекать сколько времени поток реально выполнял код, а сколько ничего не делал и ждал пока ему удастся завладеть локом.
Для теста была использована кастомная сборка Ruby 2.7.4 (флаги компиляции не изменены, только добавлен новый код), Rails 6.1.4.1 с puma 5.5 в качестве апп сервера с 1 инстансом и 5ю тредами (по умолчанию). Все это дело запускалось на моем ноутбуке HP с 4 ядрами и 8GB RAM, а также на google cloud в регионе us-central2 с инстансом e2-medium (2 vCPU, 4 GB memory). Результаты между локальной машиной и облаком в процентном соотношении примерно равны. Для измерения только рабочих воркеров пумы я еще и ее запатчил, чтобы достучаться до ее тред пула.
В качестве приложения я создал 2 эндпоинта: один для статистики, а другой рабочий
class TestingController < ApplicationController
def stats
threads = $puma_thread_poll.workers.map do
{
running: _1.running_time,
wait_gvl: _1.wait_gvl_time,
gvl_vs_running: (_1.wait_gvl_time / _1.running_time * 100).round(2),
}.tap(&:clear_timings)
end
avg_gvl_vs_running = threads.sum { _1[:gvl_vs_running] } / threads.size
render json: { avg_gvl_vs_running: avg_gvl_vs_running, threads: threads }
end
def do_work
# имитирую бурную деятельность с походами в базу и сериализацией
# жсона
50.times { User.last }
10.times { make_user }
render json: User.last(25).map(&:as_json)
end
def make_user
User.create!(email: "#{SecureRandom.hex}@mail.ru", username: SecureRandom.hex, country_code: "RU", logins_count: rand(10..150))
end
end
Методика измерения внутри руби такая: любой поток, перед тем как сможет выполняться, должен взять GIL, а после того как исчерпает свое время, он должен его вернуть. Таким образом я буду замерять 2 промежутка: непосредственно время захвата лока, а так же время между захватом и освобождением.
Код не особо интересен, но вдруг кому-то надо...
Да, да, код картинками, можете начинать хейтить, но так проще
Когда все было приготовлено я 10 раз дернул рабочий эндпоинт руками, чтобы пума подняла все свои воркеры. Время ответа вполне себе приемлимое (выбрал средний результат)
Completed 200 OK in 91ms (Views: 1.3ms | ActiveRecord: 18.6ms | Allocations: 36166)
Затем пошел глянул стату
{
"avg_gvl_vs_running":"16.09%",
"threads":[
{
"running":423187434.0,
"wait_gvl":14709976.0,
"gvl_vs_running":"3.48%"
},
{
"running":494142788.0,
"wait_gvl":1916580.0,
"gvl_vs_running":"0.39%"
},
{
"running":1263953814.0,
"wait_gvl":35301242.0,
"gvl_vs_running":"2.79%"
},
{
"running":19586474.0,
"wait_gvl":10783870.0,
"gvl_vs_running":"55.06%"
},
{
"running":17937350.0,
"wait_gvl":3359780.0,
"gvl_vs_running":"18.73%"
}
]
}
Ух ты! Всего лишь 16 процентов времени, что работал воркер, было потрачено впустую! Как вам такое, хейтеры руби? Кажется, гил не такая уж и большая проблема. Для верности нужно провести небольшое нагрузочное тестирование.
ab -n 500 -c 4 http://192.168.100.9:3000/testing/do_work
...
Concurrency Level: 4
Time taken for tests: 31.135 seconds
Complete requests: 500
Failed requests: 0
(Connect: 0, Receive: 0, Length: 0, Exceptions: 0)
Total transferred: 1645986 bytes
HTML transferred: 1142366 bytes
Requests per second: 16.06 [#/sec] (mean)
Time per request: 249.082 [ms] (mean)
Time per request: 62.270 [ms] (mean, across all concurrent requests)
Transfer rate: 51.63 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 4 10 5.6 9 45
Processing: 106 238 87.7 209 633
Waiting: 105 236 87.9 207 632
Total: 115 248 88.4 219 644
Percentage of the requests served within a certain time (ms)
50% 219
66% 233
75% 254
80% 290
90% 362
95% 491
98% 519
99% 546
100% 644 (longest request)
Ага, тут уже среднее время ответа более 200мс, что довольно таки много, интересно что же там в статистике...
{
"avg_gvl_vs_running":"94.936%",
"threads":[
{
"running":810864938.0,
"wait_gvl":714257002.0,
"gvl_vs_running":"88.09%"
},
{
"running":1022361816.0,
"wait_gvl":881835262.0,
"gvl_vs_running":"86.25%"
},
{
"running":687098840.0,
"wait_gvl":728491848.0,
"gvl_vs_running":"106.02%"
},
{
"running":706853620.0,
"wait_gvl":696489552.0,
"gvl_vs_running":"98.53%"
},
{
"running":601701804.0,
"wait_gvl":576366940.0,
"gvl_vs_running":"95.79%"
}
]
}
Вот это поворот! 94%!!! А это еще не максимум, там и болше 100 было. Таким образом при нагрузке чуть более чем 18 рпс из-за гила ответ от сервера увеличивается чуть более чем в 2 раза!!! и если убрать гил, то для обработки того же количества клиентов нужно будет в 2 раза меньше воркеров. Что ж, выводы делать не буду, просто оставлю это здесь.
PS: Я не гуру, возможно моя методика подсчета не верна, но мне она кажется логичной. С исправлениями прошу в комментарии.
UPD: Выражаю благодарность Aquahawk за то, что дал мне понять, что 18 рпс это супермало, в виду определенных причин для свего домашнего компа и мелкого гугл клауда я думал что 18 рпс это норм. А ошибка была банальна: запускал сервер в девелопмент режиме, он пишет кучу логов в консоль, после запуска в продакшн режиме я получил результаты получше
ab -n 500 -c 4 http://192.168.100.9:3000/testing/do_work
...
Concurrency Level: 4
Time taken for tests: 7.435 seconds
Complete requests: 500
Failed requests: 0
(Connect: 0, Receive: 0, Length: 0, Exceptions: 0)
Total transferred: 904000 bytes
HTML transferred: 817500 bytes
Requests per second: 67.25 [#/sec] (mean)
Time per request: 59.480 [ms] (mean)
Time per request: 14.870 [ms] (mean, across all concurrent requests)
Transfer rate: 118.74 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 4 22 101.5 11 1305
Processing: 15 37 15.4 35 194
Waiting: 15 35 14.7 33 191
Total: 21 59 102.7 47 1329
Percentage of the requests served within a certain time (ms)
50% 47
66% 50
75% 53
80% 55
90% 61
95% 69
98% 178
99% 247
100% 1329 (longest request)
С локалхоста удалось выжать 327 рпс. Так же стоит отметить что в продакшн моде расход на гил упал до 60% (но иногда там проскакивает больше ста)
{
"avg_gvl_vs_running":57.06,
"threads":[
{
"running":421967344.0,
"wait_gvl":210011148.0,
"gvl_vs_running":49.77
},
{
"running":357689510.0,
"wait_gvl":184854136.0,
"gvl_vs_running":51.68
},
{
"running":191738150.0,
"wait_gvl":148564240.0,
"gvl_vs_running":77.48
},
{
"running":348492592.0,
"wait_gvl":186578158.0,
"gvl_vs_running":53.54
},
{
"running":357769938.0,
"wait_gvl":188994538.0,
"gvl_vs_running":52.83
}
]
}
Комментарии (55)
Mox
26.09.2021 21:24Не совсем понял идею:
сам подход не оч понятный - пока один "легкий тред" залочен, это означает что вычисления происходит в другом треде. Просто в одном интерпретаторе вы ограничены одним СPU, а в "параллельной" реализации без GIL росла бы загрузка процессора одним воркером до нескольких сот процентов от 1 CPU
Из этого вывод - при большой нагрузке нужно просто увеличить количество воркеров
Но больше чем позволяет вычислительная мощность сервера вы все равно не загрузите
По моему passenger мог сам при нагрузке увеличивать количество воркеров и снижать их количество при бездействии.
motoroller95 Автор
26.09.2021 21:38Строго говоря методика расчета это величина обратная КПД, т.е. 94% времени что работал системный (не рубишный) поток ушло на ожидание гила, коэффициент бесполезного действия.
Мне кажется когда встает вопрос о том сколько воркеров поднять и сколько в них тредов держать четкого ответа нету. С одной стороны Вы правы, больше воркеров, больше конектов обработаем, однако это еще больше памяти и еще больше машин нужно. Юникорн потому и умер, что он слишком обжорный, что только процессами скейлится. Просто при выборе соотношения нужно еще и про гил помнить (как оказалось).пока один "легкий тред" залочен, это означает что вычисления происходит в другом треде
Что Вы имеете в виду под "легкий тред"? буду считать что это тред который не грузит проц, таковым может являться тот который пошел в базу. Ну так вот когда он в базу пошел он вернул гил, когда ответ с базы пришел ему гил нужно взять заново. И получается что он уже готов работать, но не может
По моему passenger мог сам при нагрузке увеличивать количество воркеров и снижать их количество при бездействии.
Пума так тоже может, но с тредами. когда нагрузка уменьшается она убьет лишние треды
Mox
26.09.2021 22:15Похоже что RoR все таки масштабируется про процессам а не по тредам. Поэтому вопрос стоит ставить в контексте "перепелаты за память".
Под легким тредом я имел ввиду тред, который на самом деле не параллельный.
>Ну так вот когда он в базу пошел он вернул гил, когда ответ с базы пришел ему гил >нужно взять заново. И получается что он уже готов работать, но не может.
Верно, потому что работает другой тред, поэтому и имеет смысл масштабировать про процессам, чтобы было поменьше залоченных тредов. Я еще не знаю как сейчас, давно была сборка ruby ee от passenger, которая форкала процессы ruby со стратегией работы с памятью "copy on write", то есть потребление памяти росло не очень быстро.
Mox
27.09.2021 00:26И еще - в rails очень удобное кэширование фрагментов view, что есть не во всех фреймворках-конкурентах.
Aquahawk
26.09.2021 21:50+218 рпс
Я всё правильно понял? Реквестов в секунду? Не тысяч а штук?
motoroller95 Автор
26.09.2021 21:5418к рпс это прям мощно, мб у каких-нибудь яндексов и есть такая нагрузка. У меня на работе в пике 80рпс, все это ложится на 2 машины с 10ю процессами пумы. А так да, Вы правы, именно 18 штук
Aquahawk
26.09.2021 22:01Тоже сходил посмотреть на одну тачку, там порядок 10к rps и примерно втрое больше конкарренси. Это правда чистый nginx но там и близко тачка не уложена. Ох как мне странно слышать такие цифры. Два сервера на 80 rps это что-то невероятное. А если к вам в 10 раз больше потребителей придёт и станет 800, оно ляжет чтоли всё?
motoroller95 Автор
26.09.2021 22:10-1а можете скинуть проект где такая нагрузка в 10к? я мб рили что-то не понимаю, но это же супердохера
Aquahawk
26.09.2021 22:13Одна из подсистем игровой компании у которой под 200 миллионов инсталлов игр. Больше деталей наверно в публичном поле не могу озвучить.
Aquahawk
26.09.2021 22:06https://habr.com/ru/post/414541/
Вот ребята обсуждают цифры порядка 10к рпс с ядра(!) На Node.js это не статику nginx'ом раздавать, да логи писать.
motoroller95 Автор
26.09.2021 22:32require 'socket' include Socket::Constants $socket = Socket.new(AF_INET, SOCK_STREAM, 0) sockaddr = Socket.sockaddr_in(2000, '0.0.0.0') $socket.bind(sockaddr) $socket.listen(1024) connections = Queue.new 5.times do fork do 5.times do Thread.new do loop do client = connections.pop client.recv(1024) client.send("hello world\n", 0) client.close end end end loop do client = $socket.accept.first connections << client end end end sleep
вот этот код на моей машине выдает 1.5к рпс, что оч мало по Вашим рассказам. Ну мб и норм что пума с рельсой в обвязке, на 5 тредах, с записью кучей логов и внутринними синхронизациями дает мне 18)
motoroller95 Автор
26.09.2021 22:36+1вот такой же только на плюсах, выдает опять таки 1.5к. Видимо это для моей машины предел
Aquahawk
26.09.2021 22:40Просто на сокетах, без http и всего прочего, в 5 потоков это невероятно мало. Блин у нас плюсовый http недавно отпубликовался, там еще доки нет, но есть перловая дока https://metacpan.org/pod/UniEvent::HTTP а вот плюсовая репа https://github.com/CrazyPandaLimited/UniEvent-HTTP без док.Надо попросить ребят цифры бенчмарков написать.
motoroller95 Автор
26.09.2021 22:49+219к рпс получил на нгинксе через
ab -n 10000 -c 4 http://localhost/
Кажется нужно разбираться, спасибо за идеюAquahawk
26.09.2021 22:50+1А вот это уже что-то достойное. Рад что открыл вам глаза на то, что вообще бывает
Aquahawk
26.09.2021 22:58Отдельно обратите внимание что по дефолту у nginx один воркер. Дав ему 5 вы и что-то близкое к 100к рпс на своей тачке можете увидеть. Линейно оно конечно не будет скейлиться, и вопрос еще как сам ab масштабируется, этого я не копал.
motoroller95 Автор
26.09.2021 23:25у меня 5 воркеров нгинкса было 19к рпс. Еще я нашел затык почему всего 18 рпс: я запускал в девелопмент (лол кек) режиме, а он пишет логи. Запустил в продакшн и получил 327 рпс, а так же процент гила упал до 60. Не обратил на это внимание сразу т.к. привык что прилага сыплет дебаг логами. При разборе продакшн инцидентов они очень помогают.
Aquahawk
26.09.2021 23:29Я цифрами не обладаю, спрошу у ребят наших за рпс на игровых серверах, но там сильно больше цифры, и логов оно пишет дай боже, гигабайтами с каждого процесса. А тот сервис что держит около 10к рпс, пишет около 50 гигабайт логов в час.
motoroller95 Автор
26.09.2021 23:36нгинкс асинхронный, рельса нет
Aquahawk
26.09.2021 23:40Наш http умеет и синхронный и асинхронный режимы, попрошу ребят забенчить в обоих, если удастся в обозримые сроки, напишу тут. А в режиме долбёжки ab сервис который никуда не ходит(ни в базу ни к другим сервисам) не должен видеть разницу синхронный он или нет
Aquahawk
26.09.2021 23:42+1Перляка у нас тоже синхронная, и каталист синхронный, оно десятки тысяч рпс конечно не тащит, но и не такие цифры.
Aquahawk
27.09.2021 00:08Вот мне подсказывают что перловый каталист, синхронный и считающийся очень медленным, с ядра, отдаёт примерно 1-2к рпс
Aquahawk
27.09.2021 08:48Вот посмотрите как выглядит по настоящему высокая нагрузка и какие rps можно выжимать из операционной системы https://www.techempower.com/benchmarks/
saboteur_kiev
27.09.2021 01:36Даже обычный форум, где онлайн 10-20к, может дать гораздо больше чем 18 рпс.
RarogCmex
25.10.2021 22:30Я на Хаскель-Хелло-Ворлд проекте спокойно выжимал четыре (!) тысячи RPS. Подозреваю, что если подкрутить пару вещей, и самого начала подойти к оптимизации параноидально, то можно получить две тысячи RPS на боевом проекте.
niko1aev
27.09.2021 00:20+118 запросов в секунду - это действительно очень мало. Не знаю, как так надо готовить Rails, чтобы получить такие низкие цифры. Но в целом 50 SELECT и 10 INSERT на один запрос к серверу - это как-то странно. Если Вы создаете 180 пользователей в секунду, то за день у Вас будет 46 млн users.
Вообще Rails из коробки выдает где-то 200-300 RPS
При хорошей настройке мы делали
- 20 000 к API в минуту ( 300 RPS, но очень тяжелых)
- 1-4 млн insert в базу в сутки
- и все это крутится на одном сервере
Плюс на входе вешается Cloudflare + Nginx Cache и Ruby on Rails в виде монолита легко на одном сервере вытягивает несколько миллионов посетителей в месяц. Причем не пользователей, который получили статики или публичную страничку, а пользователей с поисками, авторизацией, генерацией кучи данных в БД плюс работу парочки сотен человек в коллцентре и отделе по работе с клиентами.
Вообщем хороший Rails проект с сотнями тысяч визитов/посетителей в месяц отлично крутится на $100 сервере и отличненько выдерживает нагрузку c Latency 100-150-200ms.
Когда проект подрастает то стоимость серваков вырастает до сотен долларов, и по опыту база обычно кушает больше чем app сервер.
Ну и напомню, что Github (360 млн визитов в месяц по данным Similarweb) работает на Rails и отлично себя чувствует.
Давайте кстати посчитаем. По данным Similarweb у Github (входит в топ 100 сайтов в мире по посещаемости) 7,6 page per visit.
360 млн * 7,6 = 2,5 млрд page request per month
это 84 млн page request per day
это 3,5 млн page request per hour
это 972 page request per second
Если у Github 100 серверов, то одному серверу нужно обрабатывать только 9,7 page request per second. Пусть даже один page request породит 10 запросов, помимо статики - и мы получим 97 request per second.
Запрос на страницу pull request порождает 10 не статичных запросов к серверу, 5 из которых вернули 304 Not Modified.
Так что если Github хватит сотни серверов, а типичному проекту с сотнями тысяч посетителей в месяц хватит одного сервера за пару сотен долларов - то значит Rails отлично справляется со своей работойAquahawk
27.09.2021 00:28+1Чет я только сейчас понял к какой нагрузке я привык. Если пересчитать на месяц то некоторые наши апи обрабатывают десятки миллиардов https обращений в месяц на одной тачке и имеют запас в разы. Естественно на такой нагрузке традиционной базы там нет вообще.
motoroller95 Автор
27.09.2021 10:17@niko1aev @Aquahawk После очередного разбирательства выяснил что под нагрузкой я упираюсь в базу. Пока дергаю руками 1-2 запроса все летает, под бенчмарком суммарно база выходит в 200мс, при том что весь запрос занимает около 270. Копну чуть глубже, потом дам апдейт из-за чего это дело. Так что не торопимся хоронить руи)
Aquahawk
27.09.2021 10:43А мы тут попробовали шарповый http сервачок, в один логический поток
ab -n 10000 -c 4 http://test-server.dev.crazypanda.ru/echo Requests per second: 3822.06 [#/sec] (mean) ab -n 100000 -c 50 http://test-server.dev.crazypanda.ru/echo Requests per second: 6836.86 [#/sec] (mean)
По факту он жрёт конечно 2 ядра, там одно на сеть, одно на логику, но это просто шарповый сервис на коленке на потестить.
Source
28.09.2021 01:27Саму СУБД надо настроить для эффективной работы на SSD, плюс connection pool на стороне Rails. Настройки по умолчанию далеки от целей нагрузочного тестирования.
motoroller95 Автор
28.09.2021 11:58конекшн пул на стороне рельсы равен кол-ву воркеров пумы. базу поднастроить мб надо, согласен. Но суть статьи в другом была, а все свелось к мерянью рпсами(
niko1aev
27.09.2021 11:56У каждого инструмента своя зона применимости. В том месте где сотни миллионов https обращений в день оставлять на входе сервер, написанный на интерпретируемом ЯП - ну это глупо. Rails в этом контексте даже рассматривать нет смысла.
Обычный web-server на Go или Rust уже выдерживает в 50-100 раз больше чем на Ruby on Rails.motoroller95 Автор
27.09.2021 12:29Ну гитхаб же выдерживает. Шопифай тоже выдерживает. Писать все на руби при такой нагрузке мб не очень затея, а написать веб морду, которая только и будет что кнопки отображать норм затея, оно в принципе горизонтально масштабируется
Aquahawk
27.09.2021 14:18У каждого инструмента своя зона применимости.
Не спорю. В данном случае топикстартер думал что такая производительность обусловлена IO сети, я показал что нет. Сеть в современных операционах позволяет протаскивать ооочень много rps. Это не значит что на медленных языках нужно выжимать столько, это значит нужно понимать в чём проблема, и выбирать инструмент понимая ограничения которые он накладывает и цену которую придётся заплатить.
Areso
26.09.2021 21:53Спасибо, всегда было интересно, какие накладные расходы идут в тех или иных вариантах многопоточности приложений, но поскольку сам в Ruby не умею, то не выдавалось возможности проверить самому.
synacker
26.09.2021 22:09+2Да нет, если я правильно понял методику, то все логично:
Работа сервера состоит по сути из двух больших блоков - обработка ввода вывода и бизнес логика
Блок ввода вывода линейно зависит от количества данных - у вас одна сетевая карта и один интерфейс, в котором данные располагаются последовательно и последовательно извлекаются. Добавьте к этому ещё что обращение идёт через едро ос на каждый новый запрос, что тоже не мало
А вот бизнес логика, поскольку тест синтетический, имеет константную или максимум логарифмическую сложность.
Поэтому вы и видите, что ваше приложение висит в блокировке, потому что большую часть времени принимает и посылает данные
motoroller95 Автор
26.09.2021 22:12Спасибо, интересное замечание
motoroller95 Автор
26.09.2021 22:17Кстати вот за ИО тоже: если в моем синтетическом тесте все ходят в ИО то большую часть времени гил свободен и треды не должны ждать друг друга. Возможно тут действительно упирается все в то, что машина занята и приемом соединений, и базой и еще чем-то. Теоретически я могу провести тест на стейдже рабочем)
Aquahawk
26.09.2021 22:21Какой приём соединений, ну поднимите рядом ноду, nginx или еще что нибудь. Десятки тысяч рпс вот что вы увидите.
Edited: сорри, это вы же, я думал это другой человек обсуждает. Если кратко то нет, никакая сеть и диск не может так ограничивать производительность.
Source
28.09.2021 01:30Зато в CPU легко упереться. Всё-таки запускать ab на той же машине, что и испытуемый сервер - это вообще не комильфо.
Aquahawk
28.09.2021 05:57На масштабах топикстартера никакой проблемы нет. А для кейсов что я привожу вообще плохо подходит, т.к. захлёбыыается на 25к rps в силу однопоточности и неэффективности самого ab. Я тесты гонял на тачке с 64 ядра и она вообще ничего не заметила
ivankudryavtsev
27.09.2021 08:10Я так понимаю, что GIL ограничивает тогда, когда есть общие данные и многопоточность. В случае web-сервисов почти всегда можно увеличивать параллелизм за счет масштабирования количества развернутых приложений, при этом в рамках самого приложения concurrency уменьшать. Что, собственно, делается в Tornado aiohttp, etc. И без тестов понятно, что GIL эффективно работает только для длительного IO, чем больше молотит сервер тем больше мешает GIL.
motoroller95 Автор
27.09.2021 09:57Все верно, знать и понимать это одно, другое дело видеть сколько реально работали и сколько долбились в гил. То, что можно скейлить процессами это понятно, но как я уже говорил сейчас пума модная, все на потоки переходят
Aquahawk
27.09.2021 13:03Подозрительно похожие цифры в статье 6 лет назад https://habr.com/ru/company/1cloud/blog/272471/ и также народ недоумевает, как вообще так жить можно
motoroller95 Автор
27.09.2021 15:55кароче вы не поверите, но я забенчмаркал наш прод, и там 22 рпс. о чем мы тут тогда вообще говорим) там хоить и вертятся другие запросы помимо моих, но число мелкое. в общем да, рубя масштабируется наращиванием серваков. Но тут уже стоит смотреть что дешевле: купить пару машин, либо найти разрабов на жсе
shybovycha
27.09.2021 17:31+1вообще, руби (особенно с рельсами) - это не про выжимание процессора по максимуму и не про простой потоков, а про скорость разработки (благодаря удобненькому синтаксису, метапрограммированию и магии ActiveWhatever).
для перформанса на уровне потоков и памяти придется поднапрячься. тем не более, самое близкое к руби - crystal и zig, но они очень молоды. ну или какой-нибудь play framework или grails (с перформансом все же не уверен).
но любые такие оптимизации будут стоить времени разработки, отладки и поддержки (а мы говорим о многопоточных и оптимизированных по времени-памяти решениях). а это не просто "х*як-х*як - и в продакшен!"
amarao
Проблема языков старого поколения, что у них нет внутриязыковой семантики, позволяющей добавлять многопоточность и не боятся этого. Например, если бы у значения был владелец, и только он мог бы менять его, или передавать в пользование другим (теряя право менять). Вот если бы была модель владения, то RAII вместе borrow checker позволила бы спокойно работать без gil'а. И без garbage collector'а.
... но это уже был бы не руби, а язык следующего поколения на R...
shybovycha
если я правильно понимаю,
std::unique_ptr
иstd::move
есть в С++... а самое близкое на "R" - VC++ Redistributable что ли?..amarao
Нет, это Rust. Который похож на современный C++, но без легаси, отголосков С, с разумными дефолтами по move/copy семантике и явном запрете тайпалиасинга за пределами unsafe-кода.
motoroller95 Автор
Я кстати тоже поглядываю в сторону раста, но там щас с вакансиями и проектами беда, используют только как второй язык на каком-нибудь вторичном недопроекте
amarao
Я, вообще, админ, и Rust для меня - отдушина разумного инженерного дизайна, где не срезаны углы, всё правильно, имеет под собой разумную причину и не вызывает facepalm'ов. Но пару мелочей я уже на Rust'е делал (вместо C), и по ощущениям, в режиме говнокода (написать и забыть) оно не сильно медленее питона в разработке. 99% типов автоматически выводятся, пока пишешь в рамках main() про борьбу с borrowck можно забыть, короче, как нашкрябал, так и работает. С добавленным бонусом, что большинство ошибок отлавливает компилятор, а не `TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'`.
shybovycha
это была поптка тонкого троллинга, ну да ладно...
а как у раста с существующим кодом? те же существующие либы на сях и плюсах как-то туго втягиваются, если я правильно помню. не сказать чтобы они на самих сях и плюсах хорошо втягиваются, но все же?
amarao
Совместимость с so'шками есть, и для этого есть достаточно интерфейса. Оно не "туго" втягивается, просто притаскивающий so'шку должен каким-то образом компилятору объяснить, что происходит с точки зрения безопасности памяти, владения значений и т.д. В обратную сторону проще - на rust'е можно написать so'шку, и любой С-совместимый код будет видеть С-подобный ABI.
https://docs.rust-embedded.org/book/interoperability/rust-with-c.html