These tests show how bare servers behave, without “other nuances” that depend on the hands of programmers.To my great regret, the tests were not equivalent, the error just in line 1 of the code called into question objectivity and the conclusion of the article.
When testing, it turned out that all applicants work with approximately the same performance in this formulation - it was all about V8 performance. However, the implementation of the task was not superfluous - the development in each of the languages ​​made it possible to make up a significant part of the subjective assessments, which in one way or another could affect the final choice.So, we have two scenarios. The first is just the root URL greeting:
GET / HTTP/1.1
Host: service.host
HTTP/1.1 200 OK
Hello World!
GET /greeting/user HTTP/1.1
Host: service.host
HTTP/1.1 200 OK
Hello, user
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");
}
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])
}
}))
}
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);
}
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!")
}
}
}
}
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.
But you should avoid the repeated compilation of a regular expression in a loop for performance reasons.
! split , , regexp . wrk split.
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);
}
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 |
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 |
Source: https://habr.com/ru/post/338268/