С такой фразой мне кинули ссылку на статью компании Mail.Ru Group от 2015 «Как выбрать язык программирования?». Если кратко, они сравнили производительность Go, Rust, Scala и Node.js. За первое место боролись Go и Rust, но Go победил.
Как написал автор статьи gobwas (здесь и далее орфография сохранена):
В статье будет много копипасты из исходной статьи, но я надеюсь, что мне это простят.
Второй — приветствие клиента по его имени, переданному в пути URL:
Я спрятал ошибку под спойлер, дабы дать желающим возможность проверить свои навыки.
Я умышленно исправил только лишь багу, а стиль кода оставил без изменений.
Все тесты запускались на локалхосте, без всяких виртуалок, ибо лень. Будет замечательно, если автор предоставит бенчмарки со своего железа, я вставлю апдейтом, вот специально для него репозиторий с тестами, где, кстати, зафиксированы растовые библиотеки на момент написания оригинальной статьи 2015.12.17 (я надеюсь, что все).
ab
Попробуем выполнить 50 000 запросов за 10 секунд, с 256 возможными параллельными запросами.
— Подожди, — скажет читатель. — И стоило тебе строчить статью ради каких-то 500rps?! Ведь это доказывает, что не важно на чем писать, все языки одинаковые!
И тут вступает в дело мой шнур. Шнур для зарядки ноутбука, разумеется.
Стой, Go, ты куда?
Я допускаю, что статья компании Mail.Ru Group содержала непреднамеренную ошибку. Тем не менее, за 1.5 года ее прочитали 45 тысяч раз, и её выводы могли сформировать предвзятое отношение в пользу Go при выборе инструментов, ведь Mail.Ru Group, несомненно, прогрессивная и технологичная компания, к чьим словам стоит прислушаться.
И всё это время Rust совершенствовался, посмотрите на «The Computer Language Benchmarks Game» Rust vs Go за 2015 и 2017 года. Отрыв в производительности только растет.
Если тебе, дорогой читатель, по нраву Go, пиши на нём. Но не стоит сравнивать его производительность с Rust, ибо она будет не в пользу твоего любимого языка, уж точно не на таких синтетических тестах.
А если тебе нравится Rust, вливайся в сообщество, нам многого не хватает. Не хотите работать бесплатно? Присоединяйтесь к проекту Tox и пишите на Rust за деньги (пишите мне в личку).
Я надеюсь, что я был объективен и непредвзят, справедливость восторжествовала, а моя статья не содержит ошибок.
Let the Holy War begin!
Как написал автор статьи gobwas (здесь и далее орфография сохранена):
Эти тесты показывают, как ведут себя голые серверы, без «прочих нюансов» которые зависят от рук программистов.К моему большому сожалению, тесты не были эквивалентными, ошибка всего лишь в 1 строчке кода поставила под сомнение объективность и вывод статьи.
В статье будет много копипасты из исходной статьи, но я надеюсь, что мне это простят.
Суть тестов
При тестировании выяснилось, что все претенденты работают примерно с одинаковой производительностью в такой постановке — все упиралось в производительность V8. Однако реализация задания не была лишней — разработка на каждом из языков позволила составить значительную часть субъективных оценок, которые так или иначе могли бы повлиять на окончательный выбор.Итак, мы имеем два сценария. Первый — это просто приветствие по корневому URL:
GET / HTTP/1.1
Host: service.host
HTTP/1.1 200 OK
Hello World!
Второй — приветствие клиента по его имени, переданному в пути URL:
GET /greeting/user HTTP/1.1
Host: service.host
HTTP/1.1 200 OK
Hello, user
Первоначальный исходный код тестов
Node.js
var cluster = require('cluster');
var numCPUs = require('os').cpus().length;
var http = require("http");
var debug = require("debug")("lite");
var workers = [];
var server;
cluster.on('fork', function(worker) {
workers.push(worker);
worker.on('online', function() {
debug("worker %d is online!", worker.process.pid);
});
worker.on('exit', function(code, signal) {
debug("worker %d died", worker.process.pid);
});
worker.on('error', function(err) {
debug("worker %d error: %s", worker.process.pid, err);
});
worker.on('disconnect', function() {
workers.splice(workers.indexOf(worker), 1);
debug("worker %d disconnected", worker.process.pid);
});
});
if (cluster.isMaster) {
debug("Starting pure node.js cluster");
['SIGINT', 'SIGTERM'].forEach(function(signal) {
process.on(signal, function() {
debug("master got signal %s", signal);
process.exit(1);
});
});
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
} else {
server = http.createServer();
server.on('listening', function() {
debug("Listening %o", server._connectionKey);
});
var greetingRe = new RegExp("^\/greeting\/([a-z]+)$", "i");
server.on('request', function(req, res) {
var match;
switch (req.url) {
case "/": {
res.statusCode = 200;
res.statusMessage = 'OK';
res.write("Hello World!");
break;
}
default: {
match = greetingRe.exec(req.url);
res.statusCode = 200;
res.statusMessage = 'OK';
res.write("Hello, " + match[1]);
}
}
res.end();
});
server.listen(8080, "127.0.0.1");
}
Go
package main
import (
"fmt"
"net/http"
"regexp"
)
func main() {
reg := regexp.MustCompile("^/greeting/([a-z]+)$")
http.ListenAndServe(":8080", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/":
fmt.Fprint(w, "Hello World!")
default:
fmt.Fprintf(w, "Hello, %s", reg.FindStringSubmatch(r.URL.Path)[1])
}
}))
}
Rust
extern crate hyper;
extern crate regex;
use std::io::Write;
use regex::{Regex, Captures};
use hyper::Server;
use hyper::server::{Request, Response};
use hyper::net::Fresh;
use hyper::uri::RequestUri::{AbsolutePath};
fn handler(req: Request, res: Response<Fresh>) {
let greeting_re = Regex::new(r"^/greeting/([a-z]+)$").unwrap();
match req.uri {
AbsolutePath(ref path) => match (&req.method, &path[..]) {
(&hyper::Get, "/") => {
hello(&req, res);
},
_ => {
greet(&req, res, greeting_re.captures(path).unwrap());
}
},
_ => {
not_found(&req, res);
}
};
}
fn hello(_: &Request, res: Response<Fresh>) {
let mut r = res.start().unwrap();
r.write_all(b"Hello World!").unwrap();
r.end().unwrap();
}
fn greet(_: &Request, res: Response<Fresh>, cap: Captures) {
let mut r = res.start().unwrap();
r.write_all(format!("Hello, {}", cap.at(1).unwrap()).as_bytes()).unwrap();
r.end().unwrap();
}
fn not_found(_: &Request, mut res: Response<Fresh>) {
*res.status_mut() = hyper::NotFound;
let mut r = res.start().unwrap();
r.write_all(b"Not Found\n").unwrap();
}
fn main() {
let _ = Server::http("127.0.0.1:8080").unwrap().handle(handler);
}
Scala
package lite
import akka.actor.{ActorSystem, Props}
import akka.io.IO
import spray.can.Http
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.duration._
import akka.actor.Actor
import spray.routing._
import spray.http._
import MediaTypes._
import org.json4s.JsonAST._
object Boot extends App {
implicit val system = ActorSystem("on-spray-can")
val service = system.actorOf(Props[LiteActor], "demo-service")
implicit val timeout = Timeout(5.seconds)
IO(Http) ? Http.Bind(service, interface = "localhost", port = 8080)
}
class LiteActor extends Actor with LiteService {
def actorRefFactory = context
def receive = runRoute(route)
}
trait LiteService extends HttpService {
val route =
path("greeting" / Segment) { user =>
get {
respondWithMediaType(`text/html`) {
complete("Hello, " + user)
}
}
} ~
path("") {
get {
respondWithMediaType(`text/html`) {
complete("Hello World!")
}
}
}
}
Подлый удар в спину
Я спрятал ошибку под спойлер, дабы дать желающим возможность проверить свои навыки.
Don't click
Дело в том, что в примере Node.js и Go компиляция регулярного выражения происходит единожды, тогда как в Rust компиляция выполняется на каждый запрос. Про Scala ничего сказать не могу.
Выдержка из документации к regex для Rust:
Выдержка из документации к regex для Go:
Как допустили такую ошибку? Я не знаю… Для такого прямолинейного теста это является существенной просадкой в производительности, ведь даже в комментариях автор указал на тормознутость регулярок:
Упс.
Выдержка из документации к regex для Rust:
Example: Avoid compiling the same regex in a loop
It is an anti-pattern to compile the same regular expression in a loop since compilation is typically expensive. (It takes anywhere from a few microseconds to a few milliseconds depending on the size of the regex.) Not only is compilation itself expensive, but this also prevents optimizations that reuse allocations internally to the matching engines.
In Rust, it can sometimes be a pain to pass regular expressions around if they're used from inside a helper function. Instead, we recommend using the lazy_static crate to ensure that regular expressions are compiled exactly once.
For example:
#[macro_use] extern crate lazy_static; extern crate regex; use regex::Regex; fn some_helper_function(text: &str) -> bool { lazy_static! { static ref RE: Regex = Regex::new("...").unwrap(); } RE.is_match(text) } fn main() {}
Specifically, in this example, the regex will be compiled when it is used for the first time. On subsequent uses, it will reuse the previous compilation.
Выдержка из документации к regex для Go:
But you should avoid the repeated compilation of a regular expression in a loop for performance reasons.
Как допустили такую ошибку? Я не знаю… Для такого прямолинейного теста это является существенной просадкой в производительности, ведь даже в комментариях автор указал на тормознутость регулярок:
Спасибо! Я тоже думал было переписать на split во всех примерах, но потом показалось, что с regexp будет более жизненно. При оказии попробую прогнать wrk со split.
Упс.
Восстанавливаем справедливость
Исправленный тест Rust
extern crate hyper;
extern crate regex;
#[macro_use] extern crate lazy_static;
use std::io::Write;
use regex::{Regex, Captures};
use hyper::Server;
use hyper::server::{Request, Response};
use hyper::net::Fresh;
use hyper::uri::RequestUri::{AbsolutePath};
fn handler(req: Request, res: Response<Fresh>) {
lazy_static! {
static ref GREETING_RE: Regex = Regex::new(r"^/greeting/([a-z]+)$").unwrap();
}
match req.uri {
AbsolutePath(ref path) => match (&req.method, &path[..]) {
(&hyper::Get, "/") => {
hello(&req, res);
},
_ => {
greet(&req, res, GREETING_RE.captures(path).unwrap());
}
},
_ => {
not_found(&req, res);
}
};
}
fn hello(_: &Request, res: Response<Fresh>) {
let mut r = res.start().unwrap();
r.write_all(b"Hello World!").unwrap();
r.end().unwrap();
}
fn greet(_: &Request, res: Response<Fresh>, cap: Captures) {
let mut r = res.start().unwrap();
r.write_all(format!("Hello, {}", cap.at(1).unwrap()).as_bytes()).unwrap();
r.end().unwrap();
}
fn not_found(_: &Request, mut res: Response<Fresh>) {
*res.status_mut() = hyper::NotFound;
let mut r = res.start().unwrap();
r.write_all(b"Not Found\n").unwrap();
}
fn main() {
let _ = Server::http("127.0.0.1:3000").unwrap().handle(handler);
}
Я умышленно исправил только лишь багу, а стиль кода оставил без изменений.
Окружение
Все тесты запускались на локалхосте, без всяких виртуалок, ибо лень. Будет замечательно, если автор предоставит бенчмарки со своего железа, я вставлю апдейтом, вот специально для него репозиторий с тестами, где, кстати, зафиксированы растовые библиотеки на момент написания оригинальной статьи 2015.12.17 (я надеюсь, что все).
- Ноут
- Intel® Core(TM) i7-6820HQ CPU @ 2.70GHz, 4+4
- CPU Cache L1: 128 KB, L2: 1 MB, L3: 8 MB
- 8+8 GB 2133MHz DDR3
- Десктоп
- Intel® Core(TM) i3 CPU 560 @ 3.33GHz, 2+2
- CPU Cache L1: 64 KB, L2: 4 MB
- 4+4 GB 1333MHz DDR3
- go 1.6.2, released 2016/04/20
- rust 1.5.0, released 2015/12/10. Да, я специально взял старую версию Rust.
- Простите, любители Scala и Node.js, этот холивар не про вас.
Интрига
ab
Попробуем выполнить 50 000 запросов за 10 секунд, с 256 возможными параллельными запросами.
Десктоп
ab -n50000 -c256 -t10 "http://127.0.0.1:3000/
Label | Time per request, ms | Request, #/sec |
---|---|---|
Rust | 11.729 | 21825.65 |
Go | 13.992 | 18296.71 |
ab -n50000 -c256 -t10 "http://127.0.0.1:3000/greeting/hello"
Label | Time per request, ms | Request, #/sec |
---|---|---|
Rust | 11.982 | 21365.36 |
Go | 14.589 | 17547.04 |
Ноут
ab -n50000 -c256 -t10 "http://127.0.0.1:3000/"
Label | Time per request, ms | Request, #/sec |
---|---|---|
Rust | 8.987 | 28485.53 |
Go | 9.839 | 26020.16 |
ab -n50000 -c256 -t10 "http://127.0.0.1:3000/greeting/hello"
Label | Time per request, ms | Request, #/sec |
---|---|---|
Rust | 9.148 | 27984.13 |
Go | 9.689 | 26420.82 |
— Подожди, — скажет читатель. — И стоило тебе строчить статью ради каких-то 500rps?! Ведь это доказывает, что не важно на чем писать, все языки одинаковые!
И тут вступает в дело мой шнур. Шнур для зарядки ноутбука, разумеется.
Ноут на подзарядке
ab -n50000 -c256 -t10 "http://127.0.0.1:3000/"
Label | Time per request, ms | Request, #/sec |
---|---|---|
Rust | 5.601 | 45708.98 |
Go | 6.770 | 37815.62 |
ab -n50000 -c256 -t10 "http://127.0.0.1:3000/greeting/hello"
Label | Time per request, ms | Request, #/sec |
---|---|---|
Rust | 5.736 | 44627.28 |
Go | 6.451 | 39682.85 |
Стой, Go, ты куда?
Выводы
Я допускаю, что статья компании Mail.Ru Group содержала непреднамеренную ошибку. Тем не менее, за 1.5 года ее прочитали 45 тысяч раз, и её выводы могли сформировать предвзятое отношение в пользу Go при выборе инструментов, ведь Mail.Ru Group, несомненно, прогрессивная и технологичная компания, к чьим словам стоит прислушаться.
И всё это время Rust совершенствовался, посмотрите на «The Computer Language Benchmarks Game» Rust vs Go за 2015 и 2017 года. Отрыв в производительности только растет.
Если тебе, дорогой читатель, по нраву Go, пиши на нём. Но не стоит сравнивать его производительность с Rust, ибо она будет не в пользу твоего любимого языка, уж точно не на таких синтетических тестах.
А если тебе нравится Rust, вливайся в сообщество, нам многого не хватает. Не хотите работать бесплатно? Присоединяйтесь к проекту Tox и пишите на Rust за деньги (пишите мне в личку).
Я надеюсь, что я был объективен и непредвзят, справедливость восторжествовала, а моя статья не содержит ошибок.
Let the Holy War begin!
MikeLP
«За ноду и двор стреляю в упор.»
apelsyn
Ноду через модуль cluster балансировать не оптимально, для этого есть специально написанные инструменты nginx или haProxy. Поэтому по ноде тесты сомнительные.
Попробуйте так, ставим ноду 8.5
создаем index.js
Убираем балансировку и получаем тот же результат
SealTV
Сколько ещё будут сравнивать Go и Rust, очевидно, же, что Rust будет быстрее, но в тоже время разница в читаемости кода тоже существенна и тут Go выигрывает на мой взгляд.
humbug Автор
Мне очевидно, вам очевидно, а в бенчмарках Mail.Ru Group почему-то победил Go. Смотрите на таблицу в оригинальной статье под заголовком "Обобщение".
c0va23
Читаемость у Go, конечно лучше. Как минимум из-за более простого и чистого синтаксиса. Но количество кода на Go, в большинстве случаев будет гораздо больше. В некоторых случая разница в количестве кода будет в разы. А как мы все знаем, чем меньше кода — тем меньше шанс допустить ошибку.
P.S. Сам использую Go, где нужно быстро получить результат. А где важна надёжность использую Rust.
khim
Знаете, если бы читаемость была бы основным критерием, то мы сейчас писали бы на Паскале, Аде или ещё каком-нибудь потомке Алгола.
Одако нет же: весь спор идёт о том выбрали ли JavaScript, Go, Rust или PHP! А ведь все эти языки в смысле синтаксиса — С-подобные, что, как бы, явно тяжелее читается. Без подготовки, во всяком случае.
akzhan
Что? по мне так Go самый нечитаемый и клавиатурно набивательно ориентированный язык.
SealTV
Ну для меня Go более читабельный, чем Rust. Но это всё весьма субъективно и я думаю ни когда не будет согласия между всеми в этом вопросе)
kalininmr
несколько спорно.
со структурами нужно додумывать чтотам куда и как скопировалось при наследовании и все такое.
mersinvald
А не смущает, что hyper взяли старой версии, еще не асинхронный?
humbug Автор
Я взял hyper не старой версии, а ту, что была доступна автору оригинальной статьи в декабре 2015 года. Более того, я сидел на crates.io пару часов и фиксил ручками Cargo.lock, чтобы принудительно дауншифтнуть версии библиотек до того года. Иначе это была бы некорректная статья с заголовком: "Давайте сравним go 2016 года и новые модные библиотеки Rust".
mersinvald
deleted
Siemargl
За эти годы Раст сменил уже десяток версий.
Сравнивать еще было бы неплохо память/кол-во запросов. Потому что конкуренция тут будет серьезней
humbug Автор
Правильно, но я специально сделал путешествие в прошлое, чтобы Go и Rust были в равных условиях.
MacIn
А зачем? Ну, доказали вы, что старая статья — врет. Кому это интересно? Как дела обстоят сегодня?
VovanZ
Nagg
Да почти во всех бенчмарках Го против других языков примеры на этих других языках писал школьник без элементарных знаний. Вот вам пример из такого бенчмарка:
Java: (выгружаем всю таблицу в память из sql а потом находим нужную строку)
Go: (находим строку по sql запросу)
Нет, это не шутка — это публиковалось тут на хабре и много людей так же просмотрело и возможно сделали по этому бреду вывод о "тормозной джаве".
justboris
а ссылку привести можно? Без нее выглядит как необоснованный наброс.
zirix
sohabr.net/habr/post/317006/?version=207685
Было несколько статей с подобным (или этим же) кодом
justboris
так это же не Habrahabr.
mayorovp
Это было на хабре, просто автора совершенно справедливо вынудили спрятать такую чушь в черновики.
Nagg
Никто ничего не прятал :-) https://habrahabr.ru/post/316944/ 11.1к просмотров.
UPD: а нет, прошу прощения, это другая (ссылалась на ту)
mayorovp
Это совсем другой пост. Что вы нашли между ними общего?
marsermd
Это действительно не хабр, но автор собирался опубликовать эту статью на хабре. И опубликовал её. Но скрыл, возможно из-за негативной реакции сообщества.
momont
В черновиках:
7 декабря 2016 в 4:49 | сохранено в 14:29
Скорость работы Vapor по сравнению с другими веб-фреймворками
sohabr.net/habr/post/317006
PaveLiArcH
Речь о коде для теста производительности, результаты которого используются в этой статье, на которую в свою очередь ссылаются в статье о Vapor.
Интересно, какой должен быть уровень паранойи, чтобы обходить и перепроверять все высказывания в статье, на которую ссылаешься мельком?
justboris
Ну ок, показали автору на ошибку, он удалил статью, инцидент исчерпан.
Зачем пинать лежачего и вспоминать об этом здесь?
Nagg
Никто статью не удалял, вот она, висит https://habrahabr.ru/post/316944/ 11.1к просмотров.
UPD: а нет, прошу прощения, это другая (ссылалась на ту)
robert_ayrapetyan
Хз, в highloadcup только два раста в топ-50 (причем лучший — на 31 месте), при этом 10 решений на Го (лучший «чистый» Го на 15-ом).
codesign
Это говорит скорее о том, что в Rust мало людей
Так-то я и Perl вывел в top 50 ;)
robert_ayrapetyan
В перле крутой http-сервер кстати (из коробки), одно из откровений чемпионата
phponelove
Почему же? Действительно, количество людей имеющих возможность вывести некий язык «в топ» зависит от общество кол-ва людей в этом языке. Но.
У людей в расте есть дополнительная мотивация участия, меньшая конкуренция, ну и язык декларируется как лоулевел и альтернатива С/С++ — т.е. он про системщину. Декларация подобного требует от комьюнити опыта в данной сфере, а иначе как можно агитировать?
И вопрос остаётся открытым — где все эти люди? Остаётся только верить в то, что все они были заняты в это время.
TargetSan
По отзывам довольно много приходит питонистов и рубистов, которым надо оптимизировать какой-то нагруженный кусок, а писать на С/С++ боязно. Плюс — хайлоад кап был чисто русский, так что могло быть просто мало народу в русскоязычном комьюнити. Я например узнал о соревновании сильно постфактум.
phponelove
Очень много. По крайней мере агитаторов.
Аналогично. Странно, что организаторы для такой холиварной темы не оставили рабочей свою систему проверки решений и рейтинг(вне зачёта) активным на месяц. Многие(в частности, тут мелька ссылка на редит, где раст-участник жаловался на недостаток времени) просто не успели.
morozovsk
К сожалению мало людей знает про заслуги Perl на highloadcup, потому что ваша статья опубликована только на гитхабе, хотя статья хорошая и читается на одном дыхании и стоило бы поделиться ею с остальными пользователями хабра. Но выбор ваш. Я хотел написать обзорную статью по всем языкам программирования в highloadcup, но к сожалению некоторые участники указывают ненастоящие данные и боюсь ценность выводов на основе заведомо ложной информации будет стремиться к нулю. Вначале я недооценивал перл и даже думал, что кто-то в шутку указал этот стек, пока не прочитал вашу статью. Может кто-то до сих пор так считает.
unsafePtr
Я полез в репо автора, но к сожалению никакой статьи там не нашел. Никогда не видел в глаза Perl, просто интересно о чём статья.
AterCattus
github.com/Mons/hlcup/tree/master/article
Halt
mkpankov на реддите написал ретроспективный анализ своей реализации, где разобрал причины.
ProRunner
Долго думал, что такое bicycle-building, пока не перевел на русский. Пример того, как не нужно буквально переводить идиомы.
MacIn
reinventing the wheel было бы уместно.
red75prim
Зато bicyclomatic complexity попала в цитату недели.
TargetSan
Прочитайте обсуждение на реддите и две статьи здесь, на Хабре. Вы сможете увидеть, что рамки тестового задания были очень узкими — и основную роль играла оптимизация алгоритмов под конкретный случай, а не язык.
phponelove
Что мешало всем остальным языкам «оптимизировать» «свои» «алгоритмы»? Я уж не буду говорить о том, что именно язык прямо и косвенно влияет на возможность оптимизаций. Косвенно через компетенцию автора в данном контексте, а прямо через возможности языка.
Приведу простые примеры. Откуда у человека, который писал на готовом веб-стеке на том же пхп/ноде есть необходимые навыки сетевого программирования? Как вы будите оптимизировать аллокации на языке с ГЦ? А с неуправляемым ГЦ?
И таких примеров массы.
RPG18
Точно также, как буду оптимизировать аллокации на языке без ГЦ. Например на C++ стандартная практика использования пулов объектов/памяти.
phponelove
Это уже не оптимизация, а подступы через известное место. Дело в том, что в языках с ГЦ нету памяти. Как там обстоят дела с placement new и иже с ним. Такими темпами мы потонем в тоннах бойлерплейта. Одно дело иметь уже заточенные средства, а другое дело иметь язык с абсолютно обратной логикой.
Просто пример. В крестах мы взяли 100n памяти и проинициализировали её внутри логики. Память мы взяли за ноль. Как это сделать в языке с ГЦ? А никак. Мы либо будем долбить пуши в массив, либо инициализировать его два раза.
При этом не всё так просто и те же стоки в пулы не засунешь. Как мы будем реализовать строки? Через массивы? Как мы будем их инициализировать? Где мы возьмём мемкопи? Где мы возьмём конкурентоспособную реализацию сишным строкам? Реализуем? Сомневаюсь.
Это я не говорю о том, что пролетаем со всем тем рантаймом, что предоставляет нам язык. Он весь повязан на гц. И опять же — в данном случае мы опять упираемся во вторую причину.
RPG18
Пул объектов?
Сишниые строки в самом C/C++ редко использую, т.к. strlen пробегает по всей строке и это медленно.
А в C++ не так? В критичных местах в C++ да же предварительно не очищается память, просто помечаем как "свободную".
phponelove
Дело в том, что память и объекты — это разные вещи. Объекты могут и обычно несут в себе какую-то логику инициализации. Память же — это просто память.
Сишные строки как раз-таки и используются. Длинны в С++-строке это просто некая оптимизация получения длинные и не более того.
Есть всякие поиски подстрок, поиски символов и прочее. А медленно — это только в кейсе получения длинны. Остальные операции со строками требует прохода по ней и проверка символа на ноль — операция с нулей(в большинстве случаев) стоимостью. Какая разница что делать — сравнивать два указателя, либо сравнивать текущий символ с нулём?
Да, в С++ это не так. Можно выделить кусок памяти не создавая объектов, а потом создать их в уже выделенной памяти во время её обхода.
Что понимается под «очищением памяти»? Возврат страниц в систему? Подобное поведение свойственно любым менеджерам памяти. В реализации почти всех языков берут какой-нибудь jmalloc/tcmalloc/gnumallo, либо свой велосипед. Поэтому менеджер памяти в других языках не обладает какими-то резко другими свойствами.
RPG18
Дело было в placement new, который в C++ вызывает конструирование объекта. А конструирование объекта, это инициализация.
Только в C++ строки это контейнер, у которого есть lenght и capacity. И если хочу "очистить строку", то сбрасываю длину. И алгоритмы оперирующие на длину, а не на нуль символ, как работали так и работают. Поэтому да же в программах на Си, можно увидеть свою реализацию строк.
И кроме как embedded это особо никому не нужно, т.к. можно исользовать пул объектов.
mayorovp
Вот именно! В С++ есть возможность взять область памяти из пула и инициализировать ее как объект путем вызова конструктора через placement new.
В C# или Java (пример языков с GC) такой возможности нет — а значит, нужно выносить логику инициализации из конструктора в метод инициализации. При этом теряются многие языковые фишки — к примеру, поля только для чтения (readonly/final) — а ведь на их неизменяемости могут быть завязаны оптимизации на уровне JIT. Получается, оптимизация аллокаций может не ускорить программу, а замедлить.
Наконец, выделение объектов в пул — это еще и необходимость строго следить за ссылками на них, это нечто противоположное всему прошлому опыту программирования на языке со сборщиком мусора.
RPG18
Лично в Go таких проблем нет, потому что в нем есть Go sync.Pool , который вовсю используется в fasthttp. Который показал неплохой результат в прошедшем highloadcup.
mayorovp
В приведенном вами посте рассматривается пул простых объектов — массивов байт. Буферы в виде массивов байт неплохо размещаются в пуле в любом языке программирования, не только в Go.
Но изначально-то шла речь о пулах произвольных объектов.
RPG18
sync.Pool универсальный способ. Ему все равно, что хранить.
mayorovp
Да, но разработчику не все равно какие объекты помещать в пул.
MikailBag
А зачем занулять память(кроме конечно всяких сверхсекретных данных типа ключа шифрования)?
MacIn
Чтобы данные были преинициализированы. Те же логические переменные. Ссылки/указатели тоже.
RPG18
Что бы легче найти косяки с работой памяти. Если у тебя в памяти мусор, то программа на C++ еще может делать вид, что работает. А если попробует записать что-то в nullptr, то это сразу будет видно.
phponelove
Это ложная уверенность, а любая ложная уверенность способствует большему кол-во ошибок, нежели что-то иное.
В этом вся суть — мы взяли какой-то кейс и выдали его за все косяки, либо за какую-то весомую их часть. Это не так.
Разименование адреса «на прямую» достаточно редкий кейс, особенно в современном С++. new через new — уже не модно — это не жава.
Нулевой this вызывать функции позволяет, любые поля, кроме нулевого — будут не nullptr(скорее всего оно попадёт в нулевую страницу, но это просто особенность модели памяти и вас тут просто повезло, ведь вы говорили о nullptr). Все филды после 4к — уже мимо. Массивы — мимо.
Плюс, есть много кейсов, где это только навредит. Получили в оффсет ноль — не получили сегфолт, а получили мусор — получили сегфолт.
На самом деле в любой программе используется малая часть адресспейса. Сколько там там памяти на топовой ноде? Одна миллиардная доля? Даже если это мы заммапим сотни петабайт — это копейки. Это даже не один процент.
Исходя из всего этого шанс разименовывая мусор попасть не на сегфолт достаточно туманны.
phponelove
Дело было не в этом. Зачем обманывать?
Был определён кейс «выделить память и инициализировать», допустим — инициализировать очередь для обработки. На самом деле даже это неважно — просто инициализировать память.
В языка с ГЦ память будет проинициализирована ДВА раза. В С++ один раз. Плюс, ещё остаётся открытый вопрос о том, можно ли вообще аллоцировать массив за одну аллокацию.
Строк в С++ нет, как и в си. Есть std::string, но строки на нём не заканчиваются. Даже если поверить в то, что строки есть, так же в С++ есть сроки из си. С какой стороны не взгляни — утверждение неверное.
И что же из этого следует? Какое отношение это имеет к моей цитате? Это её опровергает, либо что?
*str = 0; Имеет ту же семантику. Дальше что? std::string, кстати, делает то же самое — сишное *str = 0, только к этому изменяет ещё size.
Начнём с того, что ничего не запрещает std::string изменять «нуль-символ». Т.к. в крестах нет реаллока, а у c_str() сложность константная, то срока обязана иметь капасити +1 для нуля. Уже давно даже data() нуль-терминированная.
Таким образом через что не возьми данные из std::string — сишные функции работать будут. Тут вы соврали.
Если вы хотели привести контр-пример, то надо было приводить нуль-символы в строке. Но опять же есть memchr(), memcpy().
Поэтому — это почему? Из-за lenght у std::string в С++? Сомневаюсь. Поэтому явно от С++ не зависит.
А оптимизацию для lenght можно прикрутить к чему угодно. И из этого мало что следует.
Велосипеды-обёртки поверх сишных строк строками не являются.
Не можно. Это раз, а два — к чему ваши оценки? Вы говорили о чём? О том, что можно делать то же самое, а теперь вы пытаетесь избавиться от неудобных примеров оптимизаций.
Ну и голословные утверждения про «не нужно» вы так же чем-то сможете подтвердить?
Это не очищение памяти. И с по каким таким причинам она вдруг должна «очищаться»?
0xd34df00d
В std::string вполне может быть нулевой символ до символа на позиции size, std::string продолжит работать, а сишные функции — нет.
phponelove
Это что такое?
phponelove
Я, неверное, непонятно и глупо ответил. Чтобы подобно не повторялось — дополню.
Дело в том, что отвечающий в ответе на мой комментарий описал ту ситуацию, которая уже была описана мною в том комментарии, на который он, собственно, и ответил.
0xd34df00d
А memcat какой-нибудь тоже есть, или memlen?
Если вы хотите нулевые символы в строке, то вам неизбежно придётся таскать рядом размер этой строки.
phponelove
Изначально было про то, что сишные функции для работы со строками в рамках std::string не применимы по причине отсутствия нуля после ресайза. Я доказал, что это не так.
Далее я помог автору комментария и привёл реальный пример( валидный для std::string), но невалидный для сишных строк. Вы зачем-то пришли и повторили мне то, что я итак сказал до вас. Зачем?
А теперь играете в игру «игнорирую всё» — зачем?
Зачем мне нулевые символы? Ну хорошо — придётся таскать, и?
Это отменяет что-то ранее сказанное мною, либо противоречит чему-то? Это ответ на моё утверждение намного выше?
Это? Ну дак надо отвечать на тот комментарий, а не на другой.
Да и как я уже сказал — нулевые символы это больше надуманный кейс, чем реальный. От того я его и не рассматриваю. Если хотите — пусть будет length ещё и для хранения нулевых символов. Сломается ::data(), ::c_str(), но что поделать.
0xd34df00d
Прошу прощенья, если не уловил контекст целиком.
Что до надуманности нулевых символов в строке — мне с достаточной степенью регулярности такое встречается.
data/c_str не сломается, кстати.
std::string other { str.data(), str.size() }
вполне корректно отработает, просто надо быть осторожным.phponelove
Сломается — это будет совсем не строка, что ожидается.
0xd34df00d
Кем ожидается? Разве в стандарте написано, что там не будет нулей?
phponelove
От ::data()/::c_str() ожидается нуль-терминированная строка аналогичная строке(std::string, у которой методы вызваны), а не её подстрока.
0xd34df00d
Так она вся и возвращается. А что там нули посередине — ну извините.
TargetSan
Здесь не совсем понял, кого вы защищаете, а кому пеняете.
Или уточните, о чём речь.
Эээ… пулинг и буферы, как и без ГЦ? Если конечно стек на этом языке такое позволяет.
Это как? ГЦ который сам в истерике выделяет и удаляет память? :)
phponelove
Я никого конкретного не защищаю. Защищаю одно — объективность.
Почему же? Какой-нибудь с89 не далеко ушел от паскаля, да и сотни других языков из той же весовой категории. Правда, они ничего весомого предложить не могли, но что поделать. Это свойство «эпохи».
В те времена у си даже основной киллерфичи не было — сильного компилятора. И выиграть его было проще. Т.е. ситуация была аналогична той, что есть сейчас. Только сейчас ллвм на халяву даёт такой компилятор кому угодно. Сколько языков повылазило( в частности раст).
Да, с го в данной ситуации история та же, что и с пхп/нодой. Я просто привёл более понятные примеры.
В этом и есть суть. Во многих языках возможности оптимизации аналогичные с89 и даже выше, но они не выстрелили, либо умерли. И этих возможностей мало — нужно её уметь ими пользоваться.
Поэтому это и даёт возможность людям умеющим оптимизировать код — делать это на любом языке, но — есть нюансы. И я их так же определил. Что бы вы не делали, но если язык не позволяет опуститься на тот уровень, дать ту свободу, что нужна для оптимизации — вы это не реализуете.
github.com/Mons/hlcup/blob/master/libs/Local-HTTPServer/picohttpparser.c — это что? А сколько сишного рантайма в перле?
Понимаете в чём штука, когда мы делаем подобным бенчмарки — их смысл в чём? Смысл в том, чтобы выявить инструменты и людей, которые способны реализовывать кастомные решения.
Можно взять готовый сишный код парсера хттп, прикрутить его к питону. А если завтра нам нужно будет не хттп? Мы так же будем искать сишное решение? А если его нет? Будем писать на си?
То же самое и со всем остальным. Надо отличать дефолтные кейсы от возможностей языка в общем. Ведь когда эти дефолтные кейсы кончаются — начинается ахтунг.
Я выше отвечал по этому поводу.
Я не эксперт в ГЦ, но я видел некие хинты для гц в языках. Возможно где-то пошли ещё дальше.
TargetSan
Так, я уже перестал понимать, спорим ли мы с вами или говорим об одной вещи разными словами.
Вы начали с того, что топовое решение на Rust попало только на 31-е место, а вот на Go — аж на 15-е (потом поправили что на 11-е).
Я возразил, что на таком уровне вопрос уже не в языке, а в конкретных оптимизациях.
И вы начали аргументировать, что чем толще рантайм и слой абстракций в самом языке, тем больше проблем такие оптимизации делать. С чем я согласен.
Просто уточню, что решение на Rust (раз уж за него зацепились) отставало от 1-го места по очкам менее чем вдвое. И именно в этом контексте я и написал, что в таких условиях решает не язык, а конкретные оптимизации.
phponelove
Это был не я.
Именно про это я и говорю, но. Конкретные оптимизации зависят от языка и я, так же, сказал почему.
Я спорю именно с тем, что оптимизации — это какая-то вещь в себе. Нет. Для оптимизаций нужны возможности, в том числе, со стороны языка.
Точно так же, как выбор языка не делается просто так. Он обусловлен той областью, в которой работает человек. Т.е. язык косвенно влияет и на опыт человека и на его компетенцию в данной теме.
TargetSan
Да, точно. Извиняюсь.
Эээ… ну да, я согласен. Засим, думаю, ветку можно прикрывать.
robert_ayrapetyan
Алгоритмы вообще не решали.
Все упиралось в сеть и хаки с epoll. Я в финальном был в конце (43-м), после пилюли с epoll(0) — сейчас в песочнице на 25 (учтите, что почти все ее приняли после слива в чате в последний день).
Если бы я знал о пилюле epoll(0) в день финала, мое решение было бы на 7-ом месте. Но так нельзя говорить, т.к. еще 25 человек из топ-50 могут сказать то же самое.
TargetSan
Мне понравилась идея, предложенная на следующий хайлоад. Постепенно поднимать RPS. Выиграет тот, кто дольше всех проживёт, не выходя за SLA.
phponelove
Почему же хаки? Знание и использование особенностей работы.
mayorovp
Потому что в реальных задачах epoll(0) — это не оптимизация, а напрасное пережигание электричества.
phponelove
А jvm — это не напрасное прожигание электричества? А пхп — это не напрасное прожигание электричества? А жсон, хттп и прочее — это что? Это такое же напрасное прожигание электричества.
Ведь вы же измеряли цпу-время разных решений, да и в реальном мире производительность любых решений покупается в основном за счёт электричества и железа.
А подобные кастыльные парсеры жсона так же используются в реальных задачах? А подобные роутеры так же используются в реальных задачах? Что же вы так избирательны.
Я не знаю причины по которым вы говорите то, что говорите. Но я предполагаю то, что причина тут явно не в реальных задачах, не в электричестве, а в неком недовольстве/обиде на тех, кто этот «хак» знал.
phponelove
Ведь вы же НЕ измеряли цпу-время разных решений. Естественно.
phponelove
Кто смелый — кто сможет аргументировать за минусы? А то получается странно. Минусы есть, а ответов нет. В такой ситуации ведь не скажешь, что «я просто несогласен, а ответ уже итак дан другим участником и я с ним согласен». Почему вы поступаете так несправедливо?
robert_ayrapetyan
С точки зрения сожжения CPU — безусловно и epoll(0) и пхп с jvm жгут его с КПД разной степени паршивости. Но из прикладных соображений — я представляю решение на пхп в проде, но с epoll(0) уже нет, даже банально по ssh туда влезть не удастся. Я не минусил (и не умею ;)
phponelove
Это не ответ. Было определено два тезиса: «Потому что в реальных», «а напрасное пережигание электричества».
Что из этого следует? А то, что всё что не из множества «реальных задач» и всё, что «напрасно пережигает электричество» неприменимо и является хаком.
Мною были выдвинуты контрпримеры — на что последовало молчания. Если не можете в аргументацию своих тезисов, то зачем это вообще начинать?
Опять же. Вы нарушаете условие. Применимы-ли в проде парсинг жсона и хттп так, как это сделано в некоторых(почти всех) решениях? Нет. Ну дак зачем вы суёте сюда то, чем решения заведомо не соответствует.
Так же, не важно то, что вы представляете, а что нет. Пхп просто так жрёт электричество? Жрёт. Под критерий определённый автором первоначального утверждения подходит? Подходит. Всё остальное — отношения к делу не имеет.
Вы можете начать рассуждать о том, что рабсила дороже электричества, как какие-то иные критерии дороже, но. За это решение покупается производительность. Точно так же, как в пхп покупается что-то другое.
Никто не определил, что производительность — это «неважный» критерий, а что-то иное важный.
Не верно. Епул долбит не во всех потоках одновременно, а даже если во всех — есть планировщик. Никакой ссш у вас не сломается.
Ну и вообще, кто вас сказал о том, что epoll(0) заканчивается на захардкоренном нуле? Ничего вам не мешает считать время проведённое в обработчике и на основе данной статистике рулить этим аргументом.
Я говорю не про вас, а про тех, кто минусует без аргументации.
robert_ayrapetyan
>Это не ответ. Было определено два тезиса: «Потому что в реальных», «а напрасное пережигание электричества».
Это не я отвечал если что ).
Мой тезис был такой — epoll(0) это хак. И конечно это хак! Смотрите — одно и то же решение: 43 место без epoll(0), 7 с epoll(0), изменена одна строка в коде! Это по вашему филигранная работа и особенности? Мдмашка в чистом виде.
>Опять же. Вы нарушаете условие. Применимы-ли в проде парсинг жсона и хттп так, как это сделано в некоторых(почти всех) решениях? Нет. Ну дак зачем вы суёте сюда то, чем решения заведомо не соответствует.
Тут и комментировать нечего. Конечно применимы, в реальных хайлод все это сплошь и рядом.
>Не верно. Епул долбит не во всех потоках одновременно, а даже если во всех — есть планировщик. Никакой ссш у вас не сломается.
Во всех где вызываешь epoll(0) (а это все ядра, иначе зачем он вам вообще нужен в любом виде в реальном проде?). Из личного опыта знаю, что когда все ядра заняты на 100% (а именно это происходит) доступ по ssh становится невозможным.
И вообще вы какой-то нервенный… Судя по нику — из-за расклада с PHP в highloadcup-е? Ну так там и нода утерлась, не переживайте вы так.
phponelove
Это неважно. Вы отвечаете в том контексте, которые определили до(после вас) вас. В нём же отвечал и я. Менять условия нельзя.
Ну дак перечитайте то — на что я отвечал и кому. Зачем менять условия?
Вам не нравится одна строчка? Ну что поделать.
Это не работа — это знание особенной того окружения, в котором работаете.
Тогда это решение точно так же применимо в хайлоаде. Профит даёт.
Ну и не понятно так же. Всё это соответствует критериям, которые определили выше. И определил их не я. Вы точно так же оспорили это: habrahabr.ru/post/338268/#comment_10428330
В чём причина ваших ко мне претензий?
Епул не обязательно долбить из всех потоков.
Не знаю что там у вас за опыт, но такого не может быть по определению. Да и мой опыт говорит прямо об обратном.
Даже на десктопе я могу пустить этот вайлтру в скольки угодно тредах и это мне не помешает не то что по ssh зайти, а смотреть ютуб и писать на хабре. При этом это полностью стоковое ядро.
Не из-за этого. Я уже описал причины.
TargetSan
К слову, epoll(0), или BUSY_WAIT приводит к тому, что еполл сразу возвращается, независимо от кол-ва сработавших событий. Это приводит к тому, что текущий поток крутится в чём-то наподобие спинлока вместо того, чтобы сразу отдать остатки кванта. И да — это даёт прирост — но только в очень конкретных сценариях. И таки да, в нормальном продакшене так не делают, т.к. вместо прироста будет просадка.
phponelove
Я знаю о том, что произойдёт при epoll(0). Хотя формулировка не верна — кол-во событий на ожидание не влияет — любой событие будет епул.
Кому? Дяди Васи? Что конкурирует с епуллом по ЦПУ? Неужели обработчик евентов в том же треде(как это делается во всех реализация, что я видел)?
Какие ваши доказательства?
mayorovp
Прежде всего — блокирующие операции, которые вынуждены выполняться в фоновых потоках. Скажем, у gethostbyname нет асинхронного варианта. Надо либо использовать сторонние библиотеки — либо выносить вызовы в отдельные потоки.
Далее — любые отложенные задачи. Которые могли бы нормально выполниться в периоды простоя (например, в 3-4 ночи по времени основной аудитории).
Служебные программы. Например, мониторинг. Или уже упомянутый тут ssh.
А еще процессор может уменьшить свое энергопотребление если никто не крутит его в вечном цикле.
phponelove
Какие такие операции и в каких таких потоках? Поподробнее об этом.
Им ничего не мешает выполнятся — планировщик устроен так, что он даёт равное время на исполнения всем процессам/потокам. Вам об этом сообщили, но вы продолжаете всё игнорировать и гнёте свою линию.
При этом — захардкоренный ноль — это лишь дыра в реализации участников, которые не смогли/не захотели это реализовать иначе.
Банальное ev = epoll_wait(..., !ev * timeout); решает почти указанные проблемы, а если пойти чуть дальше — это решает все проблемы.
Хак — это как максимум реализация участников, а не сам ноль в таймауте.
Ваши отсылки к нерабочему ssh — не соответствуют действительности. Повторю уже в какой раз. Не знаю откуда вы это взяли, но это неправда.
А ещё процессор может уменьшить своё энергопотребление, если не использовать пхп, либо жаву. Дальше что? И опять же — вы продолжаете все мои доводы игнорировать. Зачем?
Если есть какие-то критерии по энергопотребленю — с них и надо начинать. А с них никто не начинал и о них никто не говорил. Вы их откуда-то достали и сделали каким-то определяющими.
Да и как минимум, перед тем как заявлять — надо посчитать сколько потребляет ЭЭ те же решения на пхп на той же нагрузке, а потом сравнить — стоит ли это меньше, чем пару часов epoll(0). Хотя опять же, данная ситуация — ваши выдумки. Это свойство не epell(0), а отдельных реализация. И я уже говорил почему.
mayorovp
Бинго!
phponelove
Есть нюансы.
Это мой промах, конечно. Забыл вас спросить о том, что такое epoll(0) для того, чтобы защитить себя от подобного.
Дело в том, что из epoll(0) следует именно epoll(0). Вы говорили именно об epoll(0), а не захардкоривании там нуля. И это очень просто доказывается, в частности, этим:
Т.е. если бы epoll(0) был бы реализован не путём захардкоренного нуля, а иначе( допустим, как показал выше я) — вы бы получили тот же результат. Но — без всех тех свойств, которые вы приписывали epoll(0). Это мат.
Именно результат вы определили за хак, именно то, что позволяет его добиться — вы определили за хак. Позволяет не ноль, не реализация, а именно epoll(0). Реализация — это нюансы.
И теперь, когда я на это вышел — вы переобулись, но это неправильно и некрасиво.
mayorovp
А теперь прочитайте еще раз, кто именно писал разные комментарии. Спойлер: у вас был не один оппонент.
phponelove
Не верно. Если вы не определяете контекст — он экспортируется из ветки выше. Такие правила. Если я что-то написал в каком-то контексте — нельзя от него(вам) откреститься и сказать «я это не писал». Так это не работает. Я отвечал в рамках него. Вы отвечали точно так же в рамках него и никак иначе.
Если проще. Вы попытались наделить мою цитату своими смыслами, которых в ней не было. В частности:
Вы взяли определение epoll(0) у автора ветки — иного быть не может. Своего вы не дали.
Вот я дал своё определение — разграничил epoll(0) и паттерн его использования. Поэтому теперь я могу использовать и то и то отдельно.
Вы же этого не сделали — со всеми вытекающими. И теперь это выглядит обычной манипулцией.
mayorovp
Из контекста лично мне очевидно, что речи про алгоритм выбора тайм-аута для epoll не шло. Просто в какой-то момент часть участников услышала про нулевой тайм-аут, заменила у себя тайм-аут нулевым — и за счет этого резко поднялась в рейтинге.
Это и есть хак.
phponelove
Про алгоритм выбора таймаута речи и не идёт.
Точно так же они могли узнать про что угодно.
Ваши обвинения были в чём? В неприменимости нулевого таймаута — это не так. Нулевой таймаут не является хаком.
И результат не является хаком. Я так же об этом писал с ссылками на инициатора ветки.
Хаком назывались именно ПОЛУЧЕННЫЕ РЕЗУЛЬТАТЫ, а не метод их получения. И вы сами же этому вторили своими заявлениями про неработоспособность нулевого таймаута.
Это подмена понятий.
Нулевой таймаут — не хак. Нулевой таймаут — оптимизация. Нулевой таймаут не ограничивается захардкоренным нулём. Изначально определялось критерием хака РЕЗУЛЬТАТ. Вы выше это повторили.
А я говорил о том, что как максимум хаком можно назвать захардкоренный ноль. Но это ничего не меняет и не захардкоренный показал бы то же самое и точно так же вывел бы в топ.
Просто задача ограничить цпу-время не стояло — этим никто и не заморачивался. Я не верю в то, что участники из топ10 не осилили бы реализовать это иначе.
Поэтому такая реализация — это лишь свойство задачи. Вернее задача не требовала по иному её решать. Вы же взяли совершенно левые условия и начали с ними что-то качать. Так это не работает.
Люди создавали решения в рамках конкретных условий. И я уже доказал то, что реализовать это и для ваших условий труда не составляло. С тем же результатом.
MikailBag
del
AterCattus
Что есть "чистый"? Моя ж гошка на 11 месте.
robert_ayrapetyan
Я твою и имел ввиду (сейчас в песочном 7-ом «грязная» с хаком на С++). В финале твоя на 11, без вопросов.
AterCattus
Ах вон оно что. Я уже как-то не слежу. Надо будет глянуть, что там за хак.
robert_ayrapetyan
Хм, я теперь думаю что (Go -> C++) — это чувак полностью переписал на С++, а не Го с хаком на С++ )
AterCattus
Возможно)
Или там func main() { C.realization() } :)
mrobespierre
Автор — молодец, нашёл ошибку там, где другие и не заметили. Даже и с учётом того, что Rust — всё ещё язык экзотический и хорошо разбирались в нём из 45 тыс. просмотревших 45 в лучшем случае. И тем не менее сенсации не получилось:
humbug Автор
Хороший тезис!
hyper
тоже не самая быстрая библиотека. Именно поэтому я тестил на древней версииrustc
с дешевымhyper
, а не пытался заоптимизировать код по самое не могу.Изначальный посыл автора оригинальной статьи был хорош: для каждого языка взять "стандартный компонент" для работы с http, пострелять в него и посмотреть на оверхед рантайма и кривизну библиотеки/языка. Вот мы и посмотрели.
dmbreaker
Если уж на то пошло, то лучше было бы протестировать и на свежих версиях — оба языка не сидели на месте все это время.
akzhan
лайк за второй пункт.
я изначально и написал читаемый пример на Crystal, а страдать уйней не стал )
beduin01
Заголовок не соответствует содержанию. Правильно было назвать статью так: «Mail.ru опять обобралась». Куда этим людям поисковик делать если они публично позорятся в каждой публикации.
quantum
А это были одни и те же люди?
akzhan
не все так просто )
0xd34df00d
Ради интереса попробовал хаскелевский Snap. 12.8 мс на запрос против гошных 12.5 на моей машине. Написать что ли тоже мини-статью? :)
А не могли бы вы ещё обновить пример с Rust, чтобы собрать его с современными зависимостями? Мне это сходу сделать не удалось, а в Rust я ни в зуб ногой, к сожалению.
0xd34df00d
И это даже как-то пессимистично. Уменьшил число процессов до двух с автодетекта, получил 10.5 мс для хаскеля. Очень неплохо.
sshmakov
Может быть глупость спрошу, я этих языков не знаю, но где в Go обработка not_found и проверка метода?
gobwas
Привет! Спасибо за найденную ошибку, это действительно fail. Безусловно, это нужно будет исправить и обновить результаты замеров.
Однако, раз уж ваш комментарий перерос в отдельную публикацию, хочется указать на некоторые моменты, которые вы решили не цитировать, но которые, тем не менее, определяют контекст сравнения.
Самое главное – статья 2015 года не называлась "какой язык быстрее". Она была о выборе языка программирования, и производительность написанных серверов была лишь одним из критериев. Это написано прямо в самом первом ее абзаце.
Если иметь это в виду, то разница, которую вы получили с исправлением, никак не влияет на наш выбор Go. Более того, в тестах так же есть обработчики без логики (GET /), в которых Rust был (и наверное есть) быстрее.
Bonart
Перечитал. В статье большую часть объема занимает тестирование производительности.
Производительность идет первым параметром в итоговой таблице для сравнения, строки отсортированы именно по нему. Причем Go уступает скале и ноде по всем приведенным параметрам, кроме все той же производительности.
Дезинформировать читателей нехорошо.
gobwas
Большую часть статьи занимают бенчмарки только по тому, что это наименее субъективная вещь. Сама статья, как это сказано в заключении, не имеет шансов быть объективной. Скажите, о какой дезинформации вы говорите?
Bonart
В которой у вас объективный эпик фейл.
Фраза в кавычках — дезинформация в чистом виде. Производительность была главным критерием, а не просто "одним из".
Это утверждение с помощью статьи проверить нельзя (в статье ранжирование в первую очередь по производительности очевидно, методики расчета общего рейтинга нет), так что тоже дезинформация. Нельзя ссылкой на статью обосновать то, чего там нет.
gobwas
Как можно называть дезинформацией то, что субъективно? То, что из статьи вынесены субъективные оценки – на мой взгляд плюс, хоть и имеет побочные эффекты в виде ошибочных интерпретаций.
Если бы ваше предположение о "главном критерии" было бы верным, то почему бы нам, например, не выбрать C или ассемблер?
Bonart
Очевидно потому, что они не удовлетворяли критериям отсева перед финальным сравнением. Вот финалистов вы отранжировали по производительности.
Проблема не в результате выбора, а в том, что после исправлений он перестал быть очевиден из статьи. Такие дела.
humbug Автор
Как бы она не называлась, ущерб русскоязычному сообществу Rust вы нанесли. Нравится вам Go — пишите на нём. Кто ж спорит.
gobwas
Не пойму вашей позиции. Мы выбрали язык, на котором вот уже два года пишем сервисы. На момент выбора – Go в почте не было. На момент выбора – большинство специалистов писали на C, Perl и JavaScript. Как я уже сказал, независимо от ошибки, которую вы нашли, мы бы все равно выбрали Go. И не потому, что выбор предвзят, а по совокупности разных критериев. Вы хотите, чтобы мы передумали и начали писать на Rust?
UA3MQJ
Вы свое решение уже приняли. Тут, видимо, вопрос о тех, кто только решает, что выбрать.
PSIAlt
Выходит, человек поделившись своим трудом нанес ущерб сообществу Rust и навредил тем, кто решает что выбрать, я правильно понял?
Crandel
Это больше похоже на подтасовку результата под Go
PSIAlt
Не драматизируйте. Это больше похоже на баг. Который кстати никто не заметил, что характеризует размер и активность сообщества rust.
Crandel
Вы только доказываете, что была подтасовка в сторону Go, ведь там не было таких детских багов(всем известно, что регулярки медленные в любом языке), соответственно, автор не разобрался с Rust, а просто наваял побыстрому кода для большего хайпа.
PSIAlt
Про хайп не знаю, я не спец в этом. Вряд ли хайп помог бы автору починить проблему на проде.
Про баги — там могло не быть детских багов по двум причинам: а) автор знает Go намного лучше чем Rust (это плюс в сторону выбора Go) и б) Go не допускает глупых ошибок (это плюс в сторону выбора Go).
Bonart
В этом случае не стоит проводить тесты производительности вообще.
Что делаешь — делай хорошо.
gobwas
Все верно. Пишешь код – пиши без багов!
Bonart
Ерничать не нужно.
Если производительность не влияет на выбор, не надо включать ее в сравнение.
Если влияет, надо обеспечить минимальный уровень качества тестов.
Вы не обеспечили, причем за счет простой ошибки.
Если уровень знаний о расте настолько низок — его не должно было быть в финальном сравнении.
humbug Автор
Ну да, круто засовывать всё в main и утверждать, что Go не допускает глупых ошибок.
PSIAlt
Про ущерб сообществу это просто смешно. Оно слабое, в том числе по этой причине выбор был сделан правильно. Поясню:
В изначальной статье 3им пунктом было «Большое сообщество, позволяющее быстро найти ответы на вопросы». Т.е. сильное сообщество нужно, чтобы делать задачи без лишних головняков, а также сильное ревью и так далее. Так вот, это сообщество нашло эту кажется простую ошибку спустя только 2 года. Выходит, проблема была и до статьи, что только добавляет очков к Go.
TargetSan
То есть если я найду в любой паблик статье про произвольный язык ошибку, я смогу говорить, что у него плохое сообщество — потому что не просматривает все статьи и не ищет в них ошибки с остервенением? Вот это реально был бы фанатизм.
akzhan
В времена оны сообщество Golang было в России отнюдь не на порядок сильнее сообщества Rust. И уж тем более сообщества C++11(14, 17).
Вообще, сейчас современный C++ идеален (не ущербен по сравнению с Go).
0xd34df00d
«Идеален» и «не ущербен по сравнению с Go» — это всё-таки не синонимы, как бы не хотелось сторонникам Go :)
Плюсам ещё есть куда расти. Модули, компил-тайм-рефлексия, концепты, вот это всё. А каких-то вещей там не будет никогда, вроде сильной системы типов. При всей моей нежной любви к плюсам (и особенно извращениям на шаблонах).
TargetSan
Это вы как-то палку перегибаете. Если одна статья с бенчмарками, пусть и от Мэйл.Ру, способна нанести серьёзный вред сообществу — я уж и не знаю.
humbug Автор
Действительно, как? Мне на самом деле кинули ссылку на статью как аргумент. Да, я в 2015 году пропустил эту статью, но тот человек — нет. И у него сформировалось определенное мнение на тему Go vs Rust.
TargetSan
Проблема в том, какое определённое мнение ваши реплики, а также Crandel, сформируют о самом сообществе Rust у присутствующих здесь. СпокойнЕе надо быть. Тем более, что ребята из мэйл.ру не встали в позу, а спокойно, как адекватные люди, сказали "Упс, да, ошибочка".
ozkriff
Очень поддерживаю что перегиб по эмоциям.
Любишь раст, люби и https://www.rust-lang.org/en-US/conduct.html :)
Crandel
Что-то не заметно, чтобы оригинальная статья поменялась, а ведь ее приводят как аргумент в спорах, потому что мейл.ру, а не Вася пупкин, извесная контора
Bonart
Автор из мыла именно что встал в позу, заявив о неизменности результата в статье даже после исправления.
gobwas
Вы ошибаетесь.
Akdmeh
Вы бы все же сделали бенчмарк обновленных версий — интересно посмотреть, как поменялась производительность.
Ну и все же о Node и Scala не забывайте
acmnu
По мне, с точки зрения результата, Go и Rust одинаковы: бинарник, многопоточность, относительно небольшие накладные расходы. Небольшие проценты в одну или другую сторону не существенны и зависят скорее от фреймфорка нежели от языка программирования.
Гораздо важнее вопрос синтаксиса. И тут к обоим языкам огромное количество притензий. Какие-то из недостатков важны в конкретных проектах, какие-то не очень, но по массе притензий к ним весьма много.
Bonart
Претензии по части синтаксисиа скорее к расту.
У го отсуствие обобщений, присутсвие null, обязательный сборщик мусора вызывают куда больше вопросов, чем любые проблемы с синтаксисом.
TargetSan
А кстати какие претензии к Rust по синтаксису?
acmnu
Да я неудачно выразился. Скорее синтаксис и возможности.
UA3MQJ
akzhan
попробуйте Crystal, не факт, что вам подойдет, да и до первой версии еще несколько месяцев, но прямо глоток свежего воздуха.
UA3MQJ
Ждем от mail.ru
замерыстатьи про Go инструменты для того, чтобы подрубаться к приложению в продакшене под нагрузкой, всякие там интроспеции, профилирование, поглядеть куда утекает память. Вроде бы это pprof.А то, что Go на hello world http показывает высокий RPS все уже и так знают.
AterCattus
Таких материалов и так куча.
Antervis
Претензий по части синтаксиса и к их конкурентам (системные яп) много
ozkriff
"Системные яп" — после всех срачей вокруг "системности" go, точно хорошая идея продолжать так говорить? Понятно же, что на практике все под системностью языка что-то свое понимают и это провоцирует споры на пустом месте.
khim
Системный язык — это то, на чём пишется система (феноменально, да?), то есть то, что не привязано ни к какому конкретному языку. И, собственно, системный язый язык у нас тут один — это Rust. Ни на чём другом вы ни библиотечку для Fortran'а, ни модуль в ядро Linux'а не напишите.
Будут ли их реально на Rust писать? Хороший вопрос — поживём, увидим. Но ни на Scala, ни на Go их написать нельзя. Вернее можно «на спор» — но это уже будет в чистом виде «вырезание гланд автогеном через задний проход».
Go в этом смысле немного похож на пресловутые Лисп-машины: в отличие от многих других языков поддержка в виде C ему «для жизни» не нужна — но и поддержать какой-либо другой язык, кроме титулярного, они не могут.
mayorovp
Система — это не только ядро и библиотеки, это еще и системная оболочка, системные утилиты, а также средства разработки. Которые могут быть написаны практически на любом языке.
khim
Так-то можно любыми словами называть всё, что угодно — только тогда общаться тяжело становится. Системная оболочка, возможно, её может быть отнесена к системе (хотя кто и где видел системную оболочку, написанную, скажем, на Prolog'е?), а вот уже системные утилиты и средства разработки — точно нет. Потому что ими человек пользуется, а не другие программы.
mayorovp
Администрирование компьютера — это не практическая задача, никто обычно не покупает компьютер чтобы его администрировать (разве что в учебных целях).
khim
Тем не менее администрированием занимается человек, а не других программы, как правило.
mayorovp
Но он этим занимается для того, чтобы обеспечить работоспособность тех самых других программ.
khim
Проблема в том, что для этого он может использовать вообще любое ПО, так что подобное расширение сделает само понятие бессмысленным.
MacIn
А это откуда определение, вас не затруднит уточнить? А, википедия.
Всю жизнь трансляторы и компоновщики, интерпретаторы командной строки и т.п. входили в системное ПО, а тут на тебе — уволили.
А вы материал по своей же ссылке читали? Видимо, нет:
khim
«По историческим причинам» — это как раз и означает, что «да — было, помним, но… пора бы об этом и забыть».
MacIn
Удивительного ничего нет: в нашей, русскоязычной школе программирования сложилась своя терминология.
«исторические причины» указаны по поводы термина системный программист/системный администратор; мы же с вами о том, что такое системное ПО. Здесь же указано: «В некоторых публикациях в категорию системного программного обеспечения также относятся средства для разработка (такие как компилятор, компоновщик, или дебаггер).»
Вы приводите цитату, указывающую вариативность и берете оттуда только ту часть, что вам в строку.
Браузер, при помощи которого мы с вами общаемся. Текстовый редактор, музыкальный проигрыватель и так далее. Все прикладное ПО.
khim
Используется для правки всё того же кода.
Позволяет мне размечать файлы при создании образов операционки.
Для того, чтобы говорить «и так далее» нужно привести хоть один пример хоть чего-нибудь, что не является «системным по» в вашем определении.
Может быть использовано для разработки. Даже игры — мы с их помощую скорость JIT'а меряем.
khim
Собственно ровно в той статье в википедии текстовые редакторы прямо так и недвусмысленно отнесены к системному ПО. Так что если уж берётесь отличать системные текстовые редакторы от несистемных — приводите критерии.
mayorovp
Я считаю, это хороший повод забыть про разделение языков на системные-несистемные.
khim
Ага. А потом ещё отменим разделение на тёплые и холодные цвета, твёрдые и мягкие предметы и вообще — отменим все прилагательные нафиг. А чё? Кто-то ж перепутать может!
ozkriff
Ну вот Goшники как-то не очень согласны с этим определением, дофига где Go называется системным языком — от того поначалу было много срачей про ЦА языка и его применимость вообще. А срачи на пустом месте — плохо, потому я и прицепился.
Знаю по крайней мере про этот (обучающий?) пример: https://github.com/tsgates/rust.ko
khim
1. Включающим в себя язык Go (что очевидно).
2. Логичным (давайте не будем извращаться и придумывать определения типа «язык программирования системный, если в его названии не более трёх букв»).
3. Не включающим в себя какой-либо распространённый язык программирования (а иначе зачем вообще такое понятие, которое все языки скопом записывает в системные?).
Вот с пунктом номер 3 — обычно жёсткий напряг.
ozkriff
Вот ведь написал изначально как раз что бы в тысячный раз эта скучная фигня не повторялась, блин.
amarao
Rust ощущается как язык с порогом вхождения. Нужно повернуть мозг в правильную позицию, чтобы заработало. И этот порог — вовсе не в странных 'закорючках, :: которые &mut местами :(i32) смущают. Закорючки бытро учатся. А вот лежащая в основе модель памяти требует капитального перетряхивания сознания, причём как при переходе с Python/ruby/php/perl, так и при переходе с C.
snuk182
Как мне показалось, модель памяти Rust сильно, сильно проще таковой в небезопасных языках. Не в последнюю очередь потому что правила обращения с памятью, которые в них «неплохо бы выполнять, иначе администрация не несет ответственности», в Rust зашиты в компилятор.
amarao
Точно не проще. Она может быть «проще в практическом применении» если учитывать время на отладку, но она точно не проще для понимания.
Попробуйте объяснить, что такое
str
(не &str!) с точки зрения программы.lifetimes, ownership & borrow — всё это центральные абстракции памяти rust'а, поскольку они определяют момент выделения/очищения памяти.
Повторю, это порог вхождения. Когда осознаёшь и привыкаешь — становится легче, но сишная модель грубее и проще. Сайд-эффекты её при этом ужасающи, но для объяснения на пальцах — сильно проще.
snuk182
Про сишную не скажу, но вот многострадальный JSR 133 я понимаю и помню ровно пять минут после прочтения — рассказываемые там вещи слишком эфемерны и теоретизированы. Практика же к ним находится в не то чтобы очень простой и понятной книге Гётца, но даже эта книга несоизмеримо проще написанного почти юридическим с точки зрения понимания простыми смертными языком JSR 133. И вот по сравнению с джавовским сотонизмом модель памяти Rust легка и понятна с полуслова. Всего три постулата:
1. Не твоё — не трогай.
2. Дал попользоваться — жди когда вернут.
3. Беспорядочный доступ запрещен. Нужно меняться между потоками — вот каналы. Нужно делиться между потоками — вот мьютексы. Хочешь настоящего беспорядка — unsafe и ССЗБ.
4. (опционально) Компилятор Rust — друг. Это понимание приходит со временем
, особенно после общения с javascript.С str вообще все просто — это обычный массив в хранении, но с определенными правилами в обработке. В случае литерала данные массива зашиты в постоянную память, наружу торчит толстая ссылка (указатель + размер массива), которая жива от скобки до скобки, или пока перемещающие функции ее не съедят.
amarao
То что вы показываете это не модель памяти, а принципы использования.
Сишная модель памяти базируется на арифметрике указателей и стеке. У раста к этому добавляется понятие владения.
khim
Нифига не проще. Проще она была в далёком прошлом — когда процессор в системе был один и кеширования в нём не было предусмотрено вообще. В современном же мире сишная модель (почти ничем не отличающаяся от того, что железо предоставляет, на самом деле) — ужасающе сложна и 99.99% программистов на C её не понимают от слова «вообще». Отсюда и «сайд-эффекты».
Halt
str — это безразмерный (unsized) тип, представляющий строковое значение, а &str — ссылка на него. Примерно как void и void* в Си, только без костылей.
Сишники вполне себе обращаются с void, но почему-то все становится плохо, когда эту идею пытаются развить и строго объяснить с позиции системы типов и идеологии языка.
Вероятно потому, что с void сишник знакомится в младенчестве и просто принимает на веру, что «надо писать void, когда функция не возвращает значения, а void* это… ну просто указатель, когда не знаем, на что указываем». В итоге человек к этому привыкает и перестает об этом думать.
amarao
Аналогия с void мне нравится, однако: void не подразумевает чего-то. То есть я могу сказать, что функция возвращает void (т.е. ничего), я могу делать операции с указателями на void, но чего-либо с типом void в C нет. А в rust'е каждая константная строка — это str. Я с трудом себе могу представить декларацию void-с-содержимым в Си. В расте такое есть (let a = «hello»).
BlessMaster
удалено
amarao
Правильно. А справа, после знака равно — str. И когда я делаю slice от String, у меня тоже str. Так что же такое str?
Пока что лучшая аналогия, которую я придумал, звучит так: str — это как земля (в контексте недвижимости). Можно передать права на неё, можно выкопать из неё всю почву, построить и снести дом, но нельзя переместить землю, нельзя увезти с собой. Она просто есть. На неё можно показывать, в ней можно хранить, можно запрещать или разрешать ходить по ней, но это и всё.
0xd34df00d
В С можете, в математике нет. Если функция не возвращает ничего (вернее, если возвращаемый тип не населён), то вы не можете её вызвать.
И это иногда немножко бьёт в темплейтах :(
amarao
Ох, тут начинается что-то крайне любопытное. Вот у нас есть функция sin(1/x). У нас есть функция lim x->0 f, которая принимает f и возвращает её предел к 0.
Что возвращает lim x->0 sin(1/x)?
mayorovp
lim x->0 sin(1/x)
возвращает особое значение "результат операции не определен" или "операция недопустима". В программировании его можно представить как NaN, как исключение или как None в монаде Optional.amarao
Вы рассказываете, что оно в программировании возвращает. Можно ещё exception сделать.
А что оно в математике «возвращает»? Ничего. Тот самый void.
Другой пример. Выражение «пусть а равно нулю» какое значение возвращает? Тоже никакого.
mayorovp
Вы путаете тип и значение. Тип данных — это множество всевозможных значений. Выражение
lim x->0 f
как функция отf
может вернуть любое вещественное число или "ничего" (на самом деле там еще три вида бесконечности может получиться, но для простоты их пропустим). При подстановке конкретного параметраsin (1/x)
получается второй вариант, но это не означает что тип возвращаемого значения — "ничего". Тип возвращаемого значения —R | { NaN }
, множество действительных чисел расширенное на 1 элемент.Вообще говоря, это не выражение, это определение. Но если рассматривать его как операцию присваивания и забыть про монаду IO, то такое выражение может вернуть только 1 значение — "успех операции". Поэтому тип такого выражения — Unit, в памяти компьютера он занимает 0 байт.
mayorovp
Вы путаете
void
в понимании Си и в понимании ФП. Да, в ФП под void обычно подразумевается ненаселенный тип, что-то чего в принципе не существует.В Си, C++ и C#
void
ведет себя скорее как нормальный тип размера 0 байт — у него есть ровно 20=1 возможное значение, и именно этого значение называют "ничего".В Java
void
ведет себя странно: будучи примитивным типом, он ведет себя так же как и в Си, но при "упаковке" доVoid
его единственное значение пропадает (остается только общее для всех ссылочных типов значениеnull
). К счастью, синтаксис языка не позволяет делать такую упаковку, а потому это несоответствие остается лишь теоретической странностью.И да, соглашусь что было бы неплохо разрешить использование void в темплейтах или дженериках
phponelove
Из чего именно следует то, что он ведёт себя именно так? Это какие-то другие Си, а в моих void — это не значение.
mayorovp
Потому что функция, которая возвращает ненаселенный тип, не может существовать (если только она не принимает ненаселенный тип). А функцию, которая принимает ненаселенный тип, невозможно вызвать.
Поскольку в Си функции могут как возвращать void, так и принимает его в параметрах — это определенно не ненаселенный тип, а что-то другое.
phponelove
Я не про это спрашивал.
Это так же не ответ. Я спрашивал не то — как не ведёт себя void, а то — почему вы решили, что он ведёт себя именно так, как вы описали.
Давайте попроще. Вы написали «void ведет себя скорее как красный». Вас спросили «почему вы решили, что воид — это красный. В моих си он не красный, а чёрный».
И вы ответили что? Оно не может быть зелёным потому что это не как «бла-бла». Поскольку в Си функция не как «бла-бла» — это не зелёный, а что-то другое.
Но вам не говорили о том, что оно зелёное. Вас спросили — почему вы говорите о том, что оно скорее красное. Всё просто.
А по поводу
Нет. Никто и никак принимать void не может. void в аргументах не означает, что функция принимает какой-то воид.
По определению воид — это типа без значений. Их нет. Сигнатура функции определяет типы, а типы — это не значения. Это не значит, что воид в Си позволяет существовать тому, чего не может быть.
Существо понятие «ничего» и существует тип. Ничего. У ничего нет значений. Ничего нельзя никак и нигде использовать. Это поведение, которое не соответствует другим типам.
Т.е. типы — это некие наборы представляемых в них значений. void не может представлять себе никакого значения. Это полное его отсутствие. Воид это понятие обратное значению, обратное обычному типу.
А теперь включаем логику. Когда значение в функции, либо где-либо ещё появляется? Правильно — в момент его использования. До того момента — значение ничего не значит и ни на что не влияет.
Тем самым вызов функции void f(); В результате имеет тип воид, а значит не имеет значения. Ничего не мешает чему-то не иметь значения и мы можем это использовать, но мы не может использовать значение.
Именно так и работает void в си. Именно поэтому функции с void существуют. Существуют тогда, когда мы не пытаемся использовать значение void. Его просто нет.
Если ещё проще. Типы описывают наборы принимаемых значений. И этот набор может быть пуст. И это воид. Это не значит, что такого типа не существует, либо что у него появляется какое-то значение.
mayorovp
В теории типов существует лишь два объекта, про которые можно сказать что они означают «ничего». Это ненаселенный тип, у которого нет значений — и тип нулевого размера, у которого 1 значение.
Я не говорил что void в Си — это в точности один из них, разумеется это не так. Но void гораздо больше похож на второй вариант чем на первый.
phponelove
И воид — это именно тот тип, у которого нет значений.
Вот я и спрашиваю — чем же он похож. Я объяснил чем он похож на первый. Теперь ваша очередь.
mayorovp
habrahabr.ru/post/338268/#comment_10429858
phponelove
Это не ответ и я уже объяснял почему. Подобное меня удивляет.
А по поводу темы — ниже я пруфцанул из стандарта. Т.к. апеллировать к логике и пониманию перестало иметь смысл. Хотя сомневаюсь, что и до этого оно имело смысл.
mayorovp
Вот вам еще аргументы.
1. Размер ненаселенного типа — минус бесконечность. Это очень странный тип данных.
2. Когда вызывается функция которая принимает void — на стеке выделяется 0 байт для ее аргументов.
3. Когда вызывается функция которая возвращает void — на стеке резервируется 0 байт для возвращаемого значения.
4. В языке существует тип «указатель на void». Для ненаселенного типа такая конструкция абсурдна.
phponelove
Я уже сказал, что никаких аргументов у вас нет.
Никакого размера у void нету. Мимо.
Опять же мимо. Никакие стэки к си отношения не имеют и ничего не выделяется. Срочно стоит почитать букварь.
Аналогично.
Я даже не знаю плакать или смеяться. Чем отличаются «выделение 0 байт» от «не выделяется вообще»? К чему тут вообще ноль?
callabi находится за пределами языка и вообще это деталь реализации. С таким же успехом я могу сказать, что в вашем ФП у void не бесконечный размер и точно так же выделяется ноль.
Что из это следует? Да ничего. Отсутствие аргументов.
mayorovp
Ничем не отличается.
Не можете. Потому что, как я уже говорил, в ФП функцию принимающую Void нельзя вызвать, а возвращающую Void — нельзя создать. Вы не найдете областей памяти в работающей программе, которые можно было бы классифицировать как значение ненаселенного типа.
phponelove
Хорошо. Сколько должно выделяться под никакое значение? Чёткий ответ.
А что с ней делать? Смотреть на неё?
Для чего вообще нужен void?
Кстати — как там дела с NaN? Интегрируется ли он в логику ФП? И каким образом.
Прям как в Си.
Как удобно врать и манипулировать. Говорим о типе а потом бам — о значении.
mayorovp
Это вы так делаете, не я.
Раз пошли переходы на личность — дискуссию заканчиваю.
phponelove
Где же?
Где же?
0xd34df00d
Смотреть. Использовать как элемент построения доказательства (или контрпримера) в пруверах. Всякое такое. В реальном продакшен-коде таких фукнций нет.
В С или в языках с более целостной системой типов?
Как одно из значений типа Double, например.
phponelove
Каким образом её можно использовать в пруферах, если позвать нельзя?
В ФП.
Не, это не ответ. Именно в рамках системы типов. Естественно, что в любую логику можно просто взять и импортировать другую логику и забыть о том, что она левая.
Как именно нан интегрируется в модель ФП, в функции и значения с т.з. математики?
Я, всё же, до сир пор не понимаю чем не устраивает void в Си. Наверное только тем, что он не такой как в хаскеле. Все свойство, которые определил человек выше у него есть.
По мне так логика функций в Си намного более полная, нежели в ФП.
Насколько я понимаю( тут я совсем не эксперт), в рамках чистой функции, у воид-функции вообще нет смысла. Но ведь функции не заканчиваются на чистоте.
В моём понимании функция — это набор операций над аргументами и результат не обязательно должен быть выражен в результате — он может быть выражен и в аргументах — путём их модификации.
Я может какой-то альтернативно-одарённый и не способен понять всей мощи ФП, но смотря на всё это — когда мы можем нарушать свои правила во имя поддержания языка. Ведь в рамках этих правил он попросту бесполезен. А после выдавать эти правила за какую-то истину. По мне — это глупо. Как вам — не знаю.
Т.е. я вижу причины по которым воид сделан иначе, но я не вижу причин того — что воид с Си какой-то не такой. Он такой же.
Либо человек как-то неправильно это объяснял, либо я как-то не понял. Но вроде понял. У воида нет значений. Воид использоваться не может. Отсутствие значения не наделяет воид каким-то значением, либо не делает его значением.
И неважно какое там значение — тип это не значение. Зачем подменять понятия и спрашивать с типа свойств значения? Ничего непонятно.
0xd34df00d
Как свидетель ложного утверждения, например. Если вы вызываете такую функцию в какой-то ветке, это значит, что эта ветка никогда не может быть принята.
В ФП Void почти не используется. Даже отдельной библиотекой в том же хаскеле сделан, например: void.
Как и любой другой элемент типа Double. У NaN есть представление, в конце концов, и в алгебру даблов он вполне себе вписывается.
Если вкратце, тем, что вы не можете написать
void a = void {};
.Про это я уже писал рядом про сайд-эффекты.
Я это не распарсил.
phponelove
Её же нельзя вызвать — как я её могу вызвать?
Оно не вписывает никуда. Вы просто экспортировали логику из ieee754 в даблы. И я спрашиваю не про логику даблов, а про математику и ФП.
Хотя я заранее знаю ответ — никак, но всё же. Вдруг он есть, а я просто не знаю.
Естественно — это не имеет смысла. Логика операции = — присваивание значения, которого у воида нету.
Опять же — каким тогда образом функцию с ФП с аргументов воидом вызвать нельзя, если записать в воид воид можно? Вы уже там определитесь. Является ли воид значением, либо нет.
Вы писали обратное тому, что пишу тут и там я.
Если проще. Реальный мир состоит из NaN, сайд-эффектов и прочего, что в логику чисто функциональную не влезает. Выше я писал о конкретных противоречиях в ваших выкладках.
Таким образом всё это ФП-великолепие просто не работает и мы обязаны интегрировать туда наны, сайдэффекты и прочее. Иначе ФП не имеет смысла.
Но при этом мы декларируем что? Что логика ФП правильная, а какая-то иная не правильна. При этом логика ФП за рамками самого ФП( в рамках реального мира) несостоятельна.
Для этого кастыляются всякие кастыли и определения, которые полностью алогичны( пример с вызовом функции, которую вызвать невозможно) и существуют только в рамках нашей веры в то, что они есть и работают/соблюдаются.
Т.е. как происходит. У нас там снизу не чистота, но мы верим в то, что у нас чистота. Как?
Это у меня и вызывает вопросы. Как то, что не может существовать само по себе можно выдавать за правильную, самодостаточную концепцию?
0xd34df00d
Хороший вопрос. Функцию
Void -> Void
— никак. Функциюa -> Void
— можете. Просто вы её написать не можете. Язык с totality checker'ом это вообще не проглотит, а в хаскеле любой тип также населён значением undefined, либо, пардон за сленг, бесконечной рекурсией (которая на самом деле одна из реализаций undefined), либо чем-нибудь таким. В любом случае, конструктивно вызвать вы и такую функцию не сможете.В реальном мире NaNов нет, кстати. Но это так.
Я их как-то не увидел, и до сих пор не вижу.
Первична не логика ФП, а логика, ну, некоторой математической теории. ФП тут может возникать лишь как модель этой логики, не более.
Hask is not a category, увы.
phponelove
Пошел за пруфами в стандарт:
Стандарт считает так же. Вариант номер раз.
mayorovp
Это показывает лишь намерения создателей стандарта но не то что у них фактически получилось.
humbug Автор
Объясню доходчивей.
В
C
объявлениеvoid f(void)
— это не объявление функции, которая принимаетvoid
и возвращает его, это объявление набора инструкций, у которого есть адрес с названиемf
. И всё.Допустим, мы пишем такой С код:
И да, это валидный
C
код. Оно компилируется и работает.А вот так. По-дефолту, если не указывать тип, он равен
int
.И вот мы пришли к тому, о чем говорил наш функциональщик: к стеку. Если мы хотим "функцию", которая ничего не возвращает, опустить тип нельзя. На стеке будет выделяться
sizeof(int)
байт(а т.к. в моем примере в конце функции я не сделалreturn VALUE_OF_ANY_INT
, то функция вернёт мусор, что делает использование ее возвращаемого значения неопределенным поведением). А сишники очень любят экономить и оптимизировать. Поэтому вC
ввели синтаксический мусор в видеvoid
. При этомvoid*
— это не указатель на void, это свой особый тип, при попытке разадресации которого вы получите ошибку:Поэтому halt выше сказал:
И перестаньте думать о сишных функциях как о функциях вообще.
C
— это кроссплатформенный ассемблер, функции — это именованый адрес инструкции.mayorovp
Почему я должен думать о void как о «синтаксическом мусоре» если все его свойства кроме одного прекрасно объясняются тем что это тип нулевого размера?
humbug Автор
Какие свойства? Какое исключение?
mayorovp
Все то, что я уже перечислял в комментариях.
Исключение — невозможность создания переменной типа
void
.humbug Автор
Ну а мое высказывание "синтаксический мусор" объясняет все свойства и исключение.
mayorovp
Нет, с таким подходом каждое свойство должно объясняться отдельно.
Caefah
Это не мешает писать на Си всё. От ядер операционных систем до «кошечек». Операционка на Java?, на Rust?, на Go? Три раза хе хе хе!!!
mayorovp
Это вы к чему сказали? Я что, где-то утверждал обратное? Какое отношение загадки типизации имеют к распространенности языка?
Caefah
Сорри, хабра искажает картину комментирования. Это был ответ на другой пост.
0xd34df00d
Давайте с другой стороны. Пусть мощность множеств A и B — 0. Какова мощность множества функций из A в B? В силу некоторых соображений (матлогических и категориальных, в частности) эта мощность равна единице. То есть, существует единственная такая функция.
Пусть теперь мы вместо B (явно объявленное возвращаемое значение функции) рассмотрим возвращаемое значение вместе со всеми сайд-эффектами (у нас же не чистый язык, в конце концов), которое есть декартово произведение B на некоторое другое множество. Декартово произведение пустого множества с любым множеством пусто, поэтому мощность множества
void(void)
функций даже с учётом сайд-эффектов должна быть равна единице, что, как мы знаем, не так, их поболе будет.phponelove
Какие-то манипуляции. Мы взяли аргумент и результат. Потом каиким-то образом к ним добавились сайд-эфекты. С т.з. вашей логики сайд-эфекты находятся за рамками вашей логики и ваша логика для них не работает.
Точно так же их будет поболее при любых других аргументах — из этого ровным счётом ничего не следует. Вы получите те же результаты и без воид.
Иначе, мы расширяем понятие функции на сайд-эффекты, которые являются такими же аргументами и результатами. Таким образом мы получаем void только в одних местах и они будут идентичны для всех.
Каким образом мы проводим какие-то произведения хрен пойми чего с хрен пойми чем — мне так же неясно. Из этого произведения ровным счётом ничего не следует и все следствие из него не имеет смысла.
Сайд-эффекты это не что-то левой — это всё входит в множество аргументов/возвратов функции.
Точно так же всё это зафейлится и на void(1bit, 64bit) — либо мы за множество возьмём оба аргумента? Тогда то же будет и с сайд эффектами.
В любом случае — я не понимаю что из этого должно следовать?
0xd34df00d
Ясненько. Понятненько.
phponelove
Ваше определение чего-то чем-то не стоит ничего. Вы можете назвать что угодно чем угодно и от этого оно не станет этим просто потому, что вы это так назвали.
Вы попытались играть со мною в логику — выдавая свои определения за какие-то формальные рассуждения. Вас спросили — каким образом вы сайд эффекты записали в аргументы/возврат функции. Ведь в рамках вашей логики функция имеет только один выход — возврат. Иного не дано.
А то, что с сайд-эффектами — это совершенное иное определение, нежели то, что вы использовали вначале.
Это и есть манипуляции. Дыры в понимание подбиваются под существующую картину мира. Из этого не выйдет ничего.
Всё это маскируется под «формальными рассуждениями», которые состоят на самом деле из банального жонглирования понятиями.
В любом случае — ладно. Я в очередной раз для себя определил потолок способностей евангелистов.
0xd34df00d
Моё определение чего? Классической наивной теории множеств или теории множеств Цермело-Френкеля? Широко известной теории типов? Ну там, я не знаю, Бенджамина Пирса почитайте, что ли, я, к сожалению, не такой умный, чтобы самостоятельно все эти глупости выдумывать.
Более того, вообще говоря я могу построить произвольную систему определений, наделить её некоторыми аксиоматическими отношениями между определениями и построить на этом теорию. Будет вполне формально. Математика, собственно, так работает.
Нет. Написал специально для этого случая: «Пусть теперь мы вместо B (явно объявленное возвращаемое значение функции) рассмотрим возвращаемое значение вместе со всеми сайд-эффектами (у нас же не чистый язык, в конце концов), которое есть декартово произведение B на некоторое другое множество.». Вот то самое другое множество и есть множество всех сайд-эффектов.
Нет. Начиная от монадического формализма заканчивая тупейшим декартовым произведением.
Что до последних ваших трёх абзацев, коли хотите пассивную агрессию — получите пассивную агрессию: во-первых, так вот и перестаньте подбивать свои дыры в понимание, а вместо этого прочтите какую-нибудь базовую книгу по матлогике (вроде лекций Верещагина и Шеня), а затем догонитесь упомянутым ранее Пирсом, коли такие дискуссии вам интересны. Во-вторых, специалисты вроде вас с евангелистами вроде меня едва ли пересекутся где-то в реальной жизни, так что не думаю, что ваше определение потолка будет иметь какие-то сайд-эффекты.
Да и вообще, моя любимая картинка в тему:
0xd34df00d
Так я вроде сразу написал: «В С можете, в математике нет.»
Ну да, сиподобный void — это как
()
в каком-нибудь хаскеле.humbug Автор
Это тавтология. Попробуйте разадресовать
void*
и получите ошибку компиляции:phponelove
Поподробнее об этом, пожалуйста. Что именно становится плохо?
Некой мистической «строгостью» понимание не ограничено, логика не ограничена.
А ещё надо писать void, когда функция не принимает аргументов. Давайте поиграем в игру.
void определяет отсутствие результата, либо возврата. Как хотите это называйте. Что такое тип у указателя? Это тип возвращаемого операцией разыменования значения.
Что такое void * — это указатель для которого нет операции разыменования. *(void *) == void, то же самое что void f() {}; f(). Результата нет.
Мы не не знаем на что указываем — мы ни на что не указываем. «указатель указывает» — это детский садик. Это как функция у которой нельзя взять результат, так это — указатель, у которого так же нельзя взять результат.
int f(); и int * p; — они идентичны. Только у одной операция скобочки, а у второй звёздочка.
Тут скорее всего проблема в том, что как и сишник принимает на веру — так и представления о си — это вера. И то, что у кого-то есть вера — из этого не следует то, что без веры в си нет логики.
phponelove
Ах да, забыл про арифметику. Опять же это сайзоф поверх типа «до» звёздочки. И sizeof(void) в итоге.
Akon32
Сравнивать неэквивалентные программы и на основании этого делать вывод, какой язык быстрее? Больше бенчмарков богу бенчмарков! Конечно же победит тот язык, на котором написан не говнокод.
RPG18
Я скептически отношусь к подобным тестам, когда берут любые две реализации сервера, без изучения их работы. Т.к. есть такие штуки как zero-copy, пулы памяти, пулы потоков, ленивые вычисления и т.д.
Halt
Выше уже писали, что изначальная задумка была сравнить дефолтные инструменты в той и другой экосистеме, а не one shot велосипеды. В такой постановке вопроса это вполне имеет смысл (при корректном проведении и правильных выводах).
RPG18
Hyper из коробки в Rust?
Halt
Я так понимаю, что речь о сравнении стандартного инструментария наиболее подходящего для решения поставленной задачи. Иначе можно докатиться до холиваров и сравнения того, что входит, а что не входит в стандартную библиотеку/поставку языка.
BlessMaster
Как бы это ни было смешно, но буквально "из коробки", как и всё, что на crates.io :-)
nuald
Внесу свои пять копеек. Я портировал все оригинальные тесты на современные библиотеки, и замерил производительность на своем макбуке. Скажу сразу, что я еще не закончил, плюс макбук — далеко не серверная платформа. Тем не менее, предварительные результаты уже готовы: github.com/nuald/simple-web-benchmark#preliminary-results
Вообще, средние результаты в текущем виде меня не особо волнуют, но нельзя не заметить, что все-таки Rust может проигрывать Go в каких-то отдельных случаях (например, в зависимости от операционной системы или конфигурации железа). Но возможно, что в мой портированный код закралась ошибка или я что-то упустил, так что буду рад, если найдете и укажите на это.
Buggins
Прислал pull request — код на языке D (vibe-d).
Buggins
Немного быстрее, чем Go.
Для запуска нужны компилятор D (DMD или LDC) и DUB.
Компилировал DMD. LDC должен давать более быстрый код.
Форк.
Buggins
На github.com/nuald/simple-web-benchmark появились результаты тестирования D на macbook.
D получился даже медленней node-js.
Потестировал D (vibe.d) по сравнению с Go.
На Windows 7 x64 — vibe.d собранный DMD под x86 чуть быстрее Go только если выбрана конфигурация vibe.d libevent. Иначе в два раза медленней.
На Linux x86_64 — vibe.d собранный DMD и в default конфигурации и в libevent показывает 45K запросов в секунду, a Go — 50K запросов. Но зато D использует 90% CPU, a Go — 120%.
Теоретически сборка Vibe.d с помощью компилятора LDC вместо DMD должна показать лучшие результаты. Проверить пока не получилось.
IncorrecTSW
go version go1.9
rustc 1.22.0-nightly
Average: 0.0040 secs
Requests/sec: 63371.1408
Rust
Average: 0.0036 secs
Requests/sec: 68601.3122
AterCattus
Ну это ж был скорее тест реализации библиотеки регулярок, нежели http сервера. А в go оно очень так себе.
Laney1
регулярки в Go выполняются в среднем медленнее чем в других языках, зато всегда за линейное время. Так и было задумано (Робом Пайком)
AterCattus
Да, я в курсе, конечно, про линейное время.
Но в тесте сравнивается просто производительность типичной регулярки (не хитро продуманной).
aliev
Только стал Go изучать, а он уже не торт.
RinatUsmanov
Расслабьтесь.
У Go есть преимущества которые пока мало кто может оспорить:
1. Простота языка. Легко понять как свой код написанный год назад, так и код другого программиста.Т.е. низкий порог входа.Соответственно легче поддерживать проекты. Соответственно низкая стоимость поддержки. (Значит бизнес будет его использовать и далее)
2. Синтетические тесты это лишь меряние «письками». даже если Go уступает по этим тестам другим языкам, это не значит что он хуже или наоборот лучше. Это лишь означает что где то в каких то задачах он уступает. Никто не может сказать куда придет 2 версия. Все может кардинально поменяться в сторону производительности в нужном синтетическом тесте ))).
Antervis
всё то же самое можно сказать про python
0xd34df00d
Простота понимания кода — не единственный фактор, влияющий на стоимость поддержки. Есть ещё и стоимость внесения изменений, а тут чем более строгий компилятор и система типов, тем лучше (от раста до всяких хаскелей, да).
Buggins
Попробуй D.
Киллер фича go — goroutines есть и в D — fibers.
Плюс многое другое, чего в Go нет.
taroved
Когда используешь язык на проектах о производительности думаешь гораздо реже, чем об удовстве. Причем здесь речь идет про полрата процента разницы. Это не серьезно. Говорю как человек долекий от обоих языков
halpro
Тем, кто сидит на Java или C# смотрят на Go. Ну простите, все равно приложение на Go будет жрать больше памяти и CPU по сравнению с приложением на C++ или Rust. GC — главный тормоз этих языков!
А плюсовики ждут релизов 1.0 всех фреймворков и IDE (и плагинов) для Rust! И безболезненно перейдут на Rust и будут смеяться над мусоросборными языками.
khim
phponelove
Память в современном, да и в любом другом мире, не отделима от CPU. ЦПУ читает память и время затраченное на её чтение/запись/ожидание прямо пропорционально кол-ву этой самой памяти, при прочих равных. А они равные.
Задержки стоят ЦПУ, внезапно. Сколько их — одна большая, либо много маленьких — неважно. Хотя много — естественно дороже.
khim
Так что транжирить память — плохо, но транжирить CPU — ещё хуже.
Для компилятора, рендерера, любой «пакетной» задаче — да, важно когда вся задача будет решена. Для сервера или интерактивной программы — ой как важно. Скорость CPU-то расти перестала! Ожидать что завтра задержки из миллисекундных станут микросекундными больше нельзя…
phponelove
Нет.
Вы либо не понимаете того, о чём вам пишут, либо делаете это специально. Вам говорили не об объёмах памяти, а необходимости траты ЦПУ на на работу с ней(памятью).
Так же — вы имели ввиду под ЦПУ — не железяку, а процессорное время. Теперь вы пытаетесь подменять понятия. Зачем?
Вам тут чётко и ясно определили связь между ЦПУ и памятью. И из этого выводится то, что имеет ввиду под ЦПУ.
Не так что.
Вы что сказали?
Всё это в контексте оверхада ГЦ по сравнению с ручным управлением памятью.
Вам объяснили, что это неверно. И ответ существует именно в контексте потребления ЦПУ ГЦ. И вам так же сказали, что задержки — это ЦПУ-время. И неважно какие это задержки по продолжительности — в сумме их столько же. А на самом не столько же, а больше(го-вариант). Ведь вызов ГЦ это не только полезная работа, но оверхеды на сам вызов и уже с ним + пакетная обработка быстрее + всё это загаживает кешей больше и прочее и прочее).
Ваши попытки рассуждать о каком-то там влиянии этой задержки на разные кейсы — к теме не относятся. Об этом разговора не было.
khim
Нет. Они неравные. Это уже давно всё исследовано: чем больше вы даёте GC памяти, тем меньше ему нужно кучу сканировать и тем быстрее он работает. Примерно на 500% скорость GC сравнивается с аккуратным «ручным» распределением памяти, а если дать памяти ещё больше — то может быть и быстрее.
Не обьяснили, а заявили. Причём заявили голословное — без ссылок на исследования. Я-то надеялся, что вы вышеупомянутую статью читали, раз берётесь обо всём этом размышлять.
Вы действительно не понимаете что чушь здесь написали? Это же азы всей теории! В случае с GC затраты времени не просто зависят от того, сколько у вас «лишней» памяти, а очень сильно от этого зависят. Как и в случае с обычным распределением памяти, впрочем (локальная память для потока в каком-нибудь tcmalloc'е тоже ведь требует некоторого «запаса»).
phponelove
Удобство.
phponelove
Где? Вы берёте мою цитату и разбираете, с явным указанием что там чушь.
Начнём, пожалуй. Я отвечал на:
Каким образом разница в потреблении памяти стала определяться только наличием ГЦ? Сколько по умолчанию в го отдаётся памяти ГЦ? Сколько по памяти стоит насувать во все объекты счётчики ссылок?
Т.е. никаким ГЦ контекст не ограничивался и это всё не более, чем попытка врать.
К ГЦ это не имеет никакого отношения. То же самое работает и для обычных аллокаторов.
Опять же.
Ни о каких объёмах памяти для ГЦ я не говорил. Я говорил о паузах. Очередная попытка врать.
Ничего не исследовано. Го/жаву вам в руки и вперёд срывать покровы. Можете начать с хайлоадкаппа, а закончить какой-нибудь in-memory database.
Не верно. Это результаты протухшие — раз. Для 3-4бенчмарков из spec, которым а) никто не видел, б) вы не воспроизведёте их результаты.
Ни о каком аккуратном режиме речи не шло — никаких свидетельств о том какое он — в статье нету. Это всё не более, чем бла-бла.
Ну и да, ручная аллокация не заканчивается на маллоке. Заканчивается она им только в подобных «исследованиях».
Опять же — бла-бла.
Я вам не буду говорить о том на что годны эти ссылки. Есть реальный мир. Го вам в руки и вперёд срывать покровы.
Вот типичный пример вранья. Я говорил о времени пауз в ответ на рассуждения о времени пауз. Ни о каких затратах памяти речь не шла. Нигде. И вы этого не покажете.
И сейчас меня цитируют БЕЗ СЛОВА О ПАМЯТИ, но рассказывают о том, ЧТО Я ГОВОРИЛ О ПАМЯТИ.
Обычное распределение памяти находится далеко за рамками маллока. Эти все рассуждения ничего не стоят вне академической среды. Как и х5 по памяти.
Сравнения глупые, некорректные и не реализуемые в реальности. Чисто попускать хелворды.
beduin01
>Тем, кто сидит на Java или C# смотрят на Go.
Интересно почему?)) После C# Go выглядит еще более убого чем Basic.
Akon32
Да и после Java тоже не очень.
Caefah
Все те приверженцы учений дедушки Оккама, кто пересыщен убогими решениями с автоматизацией работы памяти, гарбидж коллекторами, нестрогой типизацией, виртуальными машинами и прочим дерьмом типа вывертов ООП уже давно пишут на чистом Си и чувствуют себя сыто и комфортно. ))
0xd34df00d
Это в С-то строгая типизация?
Caefah
Бес попутал ) Хотел сказать динамическая типизация, в противовес статической.
halpro
И в этих синтетических «попугаях» не учитывают, что вытворяет код и данные приложения под нагрузкой.