今Rakuにしてやるからな
俺たちはいつ商用サービスをRakuにできるのか?
Rakuを知らない人向けの説明: Raku === Perl6
実は整っているRaku環境
インストールが面倒だと思っていませんか
今ならmiseで一発で入って最高です
code:bash
$ mise install raku@2025.11
gem相当
zef
RubyGems.org相当
raku.land
fez
Bundler相当
zef
(但しlockの仕組みがない、なぜ……)
Web App Framework
Cro
テンプレートエンジン
Template::Mustache
Template6
ORM
Red
(なんと! ActiveRecordパターンを実装しておりmigration機能もあるぞ!!)
HTTPクライアント
HTTP::UserAgent
JSON Alt
JSON::Fast
簡単なecho-serverで性能を見てみましょう
僕は毎年1月1日にRakuの最新版でecho-serverを動かしてみて性能を見るということをしています。
benchmarker
code:bash
#!/bin/bash
set -e
HOST="${HOST:-127.0.0.1}"
PORT="${PORT:-8080}"
REQUESTS="${REQUESTS:-1000}"
CONCURRENCY="${CONCURRENCY:-10}"
OUTPUT_DIR="${OUTPUT_DIR:-./results}"
BASE_URL="http://${HOST}:${PORT}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p "$OUTPUT_DIR"
echo "==================================="
echo "HTTP Echo Server Benchmark"
echo "==================================="
echo "Target: $BASE_URL"
echo "Requests: $REQUESTS"
echo "Concurrency: $CONCURRENCY"
echo "Timestamp: $TIMESTAMP"
echo "==================================="
echo ""
check_server() {
if ! curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/" | grep -q "200"; then
echo "Error: Server is not responding at $BASE_URL"
echo "Please start the server first: raku echo-server.raku"
exit 1
fi
echo "Server is responding."
echo ""
}
run_ab_benchmark() {
local name=$1
local method=$2
local path=$3
local extra_args=${4:-}
echo "--- Benchmark: $name ---"
echo "Method: $method, Path: $path"
echo ""
local result_file="$OUTPUT_DIR/${TIMESTAMP}_${name}.txt"
if "$method" = "POST" ; then
echo '{"test": "data"}' > /tmp/bench_post_data.json
ab -n "$REQUESTS" -c "$CONCURRENCY" -T "application/json" -p /tmp/bench_post_data.json $extra_args "${BASE_URL}${path}" 2>&1 | tee "$result_file"
rm -f /tmp/bench_post_data.json
else
ab -n "$REQUESTS" -c "$CONCURRENCY" $extra_args "${BASE_URL}${path}" 2>&1 | tee "$result_file"
fi
echo ""
echo "Result saved to: $result_file"
echo ""
}
extract_summary() {
local file=$1
echo "--- Summary from $file ---"
grep -E "(Requests per second|Time per request|Transfer rate|Failed requests)" "$file" || true
echo ""
}
main() {
check_server
echo "Starting benchmarks..."
echo ""
# GET request benchmark
run_ab_benchmark "get_simple" "GET" "/"
# POST request benchmark
run_ab_benchmark "post_json" "POST" "/echo"
# GET with path parameters
run_ab_benchmark "get_with_path" "GET" "/api/v1/users/123"
echo "==================================="
echo "Benchmark Summary"
echo "==================================="
for result_file in "$OUTPUT_DIR"/${TIMESTAMP}_*.txt; do
if -f "$result_file" ; then
extract_summary "$result_file"
fi
done
echo "All results saved in: $OUTPUT_DIR"
echo "Done!"
}
main "$@"
これを10000 requests, 100 concurrentで流す。
Raku
code:perl
my $port = %*ENV<PORT> // 8080;
my $server = IO::Socket::Async.listen('0.0.0.0', $port.Int);
say "HTTP Echo Server listening on port $port";
react {
whenever $server -> $connection {
whenever $connection.Supply(:bin) -> $data {
my $request = $data.decode('utf-8');
my @lines = $request.lines;
my $request-line = @lines0 // '';
my ($method, $path, $version) = $request-line.split(/\s+/);
my %headers;
my $body-start = 0;
for @lines.kv -> $i, $line {
if $i == 0 { next; }
if $line eq '' {
$body-start = $i + 1;
last;
}
if $line ~~ /^(\S+)\:\s*(.*)$/ {
%headers{$0.Str.lc} = $1.Str;
}
}
my $body = @lines$body-start..*.join("\n");
my %response-data = %(
method => $method // '',
path => $path // '',
headers => %headers,
body => $body,
);
my $json-body = to-json(%response-data);
my $content-length = $json-body.encode('utf-8').bytes;
my $response = qq:to/END/;
HTTP/1.1 200 OK\r
Content-Type: application/json\r
Content-Length: $content-length\r
Connection: close\r
\r
$json-body
END
$connection.print($response);
$connection.close;
}
}
whenever signal(SIGINT) {
say "\nShutting down server...";
done;
}
}
sub to-json(%data --> Str) {
my @parts;
for %data.kv -> $key, $value {
my $json-value = value-to-json($value);
@parts.push: qq/"$key": $json-value/;
}
return '{' ~ @parts.join(', ') ~ '}';
}
multi sub value-to-json(Str $value --> Str) {
my $escaped = $value.trans('\\', "\n", "\r", "\t" => '\"', '\\\\', '\n', '\r', '\t');
return qq/"$escaped"/;
}
multi sub value-to-json(Hash $value --> Str) {
return to-json($value);
}
multi sub value-to-json(Any $value --> Str) {
return qq/"{ $value.Str }"/;
}
Raku 2021.12: 1271.73 reqs/sec
Raku 2022.12: 1411.64 reqs/sec
Raku 2023.12: 1403.89 reqs/sec
Raku 2024.12: 1469.11 reqs/sec
Raku 2025.11: 2105.51 reqs/sec
最新バージョンで突然性能が良くなっている!!!
Perl
code:perl
#!/usr/bin/env perl
use strict;
use warnings;
use IO::Socket::INET;
use JSON::XS;
my $port = $ENV{PORT} // 8080;
my $server = IO::Socket::INET->new(
LocalAddr => '0.0.0.0',
LocalPort => $port,
Proto => 'tcp',
Listen => SOMAXCONN,
ReuseAddr => 1,
) or die "Cannot create server: $!";
print "HTTP Echo Server listening on port $port\n";
while (my $client = $server->accept()) {
eval {
my $request_line = <$client>;
last unless defined $request_line;
my ($method, $path, $version) = split /\s+/, $request_line;
my %headers;
while (my $line = <$client>) {
last if $line eq "\r\n";
if ($line =~ /^(^:+):\s*(.*)$/) {
$headers{lc($1)} = $2;
$headers{lc($1)} =~ s/\r?\n$//;
}
}
my $body = '';
if (my $content_length = $headers{'content-length'}) {
read($client, $body, $content_length);
}
my $response_data = {
method => $method // '',
path => $path // '',
headers => \%headers,
body => $body,
};
my $json = JSON::XS->new->utf8;
my $json_body = $json->encode($response_data);
my $content_length = length($json_body);
my $response = "HTTP/1.1 200 OK\r\n";
$response .= "Content-Type: application/json\r\n";
$response .= "Content-Length: $content_length\r\n";
$response .= "Connection: close\r\n";
$response .= "\r\n";
$response .= $json_body;
print $client $response;
};
if ($@) {
warn "Error: $@";
}
close($client);
}
close($server);
Perl 5.42.0: 32521.70 reqs/sec
Perlは速い
Ruby
code:ruby
# frozen_string_literal: true
require 'socket'
require 'json'
port = ENV.fetch('PORT', 8080).to_i
server = TCPServer.new('0.0.0.0', port)
puts "HTTP Echo Server listening on port #{port}"
loop do
client = server.accept
request_line = client.gets
break if request_line.nil?
method, path, _version = request_line.split
headers = {}
while (line = client.gets) && line != "\r\n"
key, value = line.split(':', 2)
headerskey.downcase.strip = value&.strip
end
body = ''
if headers'content-length'
content_length = headers'content-length'.to_i
body = client.read(content_length)
end
response_data = {
method: method,
path: path,
headers: headers,
body: body
}
json_body = JSON.generate(response_data)
response = "HTTP/1.1 200 OK\r\n"
response += "Content-Type: application/json\r\n"
response += "Content-Length: #{json_body.bytesize}\r\n"
response += "Connection: close\r\n"
response += "\r\n"
response += json_body
client.print(response)
client.close
rescue StandardError => e
warn "Error: #{e.message}"
client&.close
end
Ruby 3.4.7 32873.76
Ruby 4.0.0preview2 (ZJIT enabled): 33315.57
Rubyは速い
俺はこう思ったッス
Rakuは遅いのがガチのマジで問題
遅いと使われない (1st choiceにならない)、使われないと良くならない……という悪循環に陥いっているのでは
何が遅いのかがわからない
全部遅いというのはそうだと思う
NQPの効率
MoarVMの実装
そもそも言語仕様がダイナミックすぎ、最適化が難しい
とはいえどこが特に?
言語開発用のプロファイラが必要なのではないか
とはいえバージョンを追うごとに速くなっているというのはそうっぽい
3年で1.5倍良くなっている
このままのペースだと21年後に現在のRuby並の性能を獲得できる可能性がある
Rakuになる日はまだ遠そう
余談
JSON::Fast を使うとベンチマークが均質になるようだ。
code:perl
use JSON::Fast;
my $port = %*ENV<PORT> // 8080;
my $server = IO::Socket::Async.listen('0.0.0.0', $port.Int);
say "HTTP Echo Server listening on port $port";
react {
whenever $server -> $connection {
whenever $connection.Supply(:bin) -> $data {
my $request = $data.decode('utf-8');
my @lines = $request.lines;
my $request-line = @lines0 // '';
my ($method, $path, $version) = $request-line.split(/\s+/);
my %headers;
my $body-start = 0;
for @lines.kv -> $i, $line {
if $i == 0 { next; }
if $line eq '' {
$body-start = $i + 1;
last;
}
if $line ~~ /^(\S+)\:\s*(.*)$/ {
%headers{$0.Str.lc} = $1.Str;
}
}
my $body = @lines$body-start..*.join("\n");
my %response-data = %(
method => $method // '',
path => $path // '',
headers => %headers,
body => $body,
);
my $json-body = to-json(%response-data);
my $content-length = $json-body.encode('utf-8').bytes;
my $response = qq:to/END/;
HTTP/1.1 200 OK\r
Content-Type: application/json\r
Content-Length: $content-length\r
Connection: close\r
\r
$json-body
END
$connection.print($response);
$connection.close;
}
}
whenever signal(SIGINT) {
say "\nShutting down server...";
done;
}
}
Raku 2021.12 2191.02 reqs/sec
Raku 2024.12 2434.38 reqs/sec
Raku 2025.11 2379.55 reqs/sec
つまり単純にRaku built-inのJSONが遅いのか???????
余談2
Croを使うと更に遅くなる。
code:perl
use Cro::HTTP::Router;
use Cro::HTTP::Server;
use JSON::Fast;
my $port = (%*ENV<PORT> // 8080).Int;
my $application = route {
post -> *@path {
request-body -> $body {
my $req = request;
my %headers;
for $req.headers -> $header {
%headers{$header.name.lc} = $header.value;
}
my %response-data = %(
method => $req.method,
path => '/' ~ @path.join('/'),
headers => %headers,
body => $body,
);
content 'application/json', to-json(%response-data);
}
}
get -> *@path {
my $req = request;
my %headers;
for $req.headers -> $header {
%headers{$header.name.lc} = $header.value;
}
my %response-data = %(
method => $req.method,
path => '/' ~ @path.join('/'),
headers => %headers,
body => '',
);
content 'application/json', to-json(%response-data);
}
put -> *@path {
request-body -> $body {
my $req = request;
my %headers;
for $req.headers -> $header {
%headers{$header.name.lc} = $header.value;
}
my %response-data = %(
method => $req.method,
path => '/' ~ @path.join('/'),
headers => %headers,
body => $body,
);
content 'application/json', to-json(%response-data);
}
}
delete -> *@path {
my $req = request;
my %headers;
for $req.headers -> $header {
%headers{$header.name.lc} = $header.value;
}
my %response-data = %(
method => $req.method,
path => '/' ~ @path.join('/'),
headers => %headers,
body => '',
);
content 'application/json', to-json(%response-data);
}
};
my Cro::Service $server = Cro::HTTP::Server.new(
:host('0.0.0.0'),
:$port,
:$application,
);
$server.start;
say "Cro HTTP Echo Server listening on port $port";
react {
whenever signal(SIGINT) {
say "\nShutting down server...";
$server.stop;
done;
}
}
Raku 2025.11: 1097.97 reqs/sec
恐らくCroが持つRack/Plack的レイヤーのオーバーヘッドが大きいのだと思う。まあ素のサーバーよりリッチというのはそうなので理解はできる。深く調べてはいない。
まとめ
Rakuにはパフォーマンス改善の金脈が沢山ありそうです。みんなで良くしよう、Raku lang。