Швидкість роботи Vapor порівняно з іншими веб-фреймворками

Правда, що Vapor насправді такий швидкий і безпечний, як кажуть його автори? Сьогодні ми подивимося на швидкість роботи Swift у серверній частині програми в порівнянні з Ruby, PHP, JS, Python, Java, C#, Go!




У своїй минулій публікації я торкнувся теми швидкості роботи веб-фреймворку як Vapor, розробники обіцяють, що він буде працювати до 100 разів швидше інших фреймворків у ваших проектах, але одних слів мало. Давайте поглянемо на результати офіційних бенчмарків від Qutheory ( далі переклад )

Учасники тесту:

  • Vapor (Swift)
  • Ruby on Rails (Ruby)
  • Laravel (PHP)
  • Lumen (PHP)
  • Express (JavaScript)
  • Django (Python)
  • Flask (Python)
  • Spring (Java)
  • Nancy (C#)
  • Go (без фреймворків)


Тести:

  • Простий текст
  • JSON
  • Випадковий SQLite запит


Наступні команди запускалися тричі для кожного фреймворку на роздільному Digital Ocean Води.

wrk -d 10 -t 4 -128 c http://<host>:<port>/plaintext
wrk -d 10 -t 4 -128 c http://<host>:<port>/json
wrk -d 10 -t 4 -128 c http://<host>:<port>/sqlite-fetch


РезультатVapor і Express виявилися якнайшвидшими в рамках конкурсу, конкуруючи з чистим Go.


Простий текст

Тест на обробку простого тексту є самим простим, а його результати показують максимальну швидкість роботи для кожного фреймворка. Дивно наскільки близько Vapor підібрався до Go. Чистий Swift HTTP сервер заснований на потоках, у той час як Go використовує співпрограми. У деяких випадках співпрограми набагато швидше, але вони вимагають додаткових бібліотек і установки. Цілком можливо що Vapor прийме цей спосіб паралелізму в майбутньому. Крім того, Swift на Linux ще в беті, тому компілюється неоптимізованих toolchains. З новим компілятором Swift має всі шанси скинути Go.

image

JSON

Будучи написаним на JavaScript, Express отримує в цьому тесті перевага (JSON означає JavaScript Object Notation, якщо хто не знав). Vapor займає почесне третє місце із-за недосконалого синтаксичного аналізу JSON на Linux, але все одно залишається як мінімум в три рази швидше більшості фреймворків.

image

SQLite запит

З величезним відривом Express вирвався вперед, а Go напрочуд займає четверту позицію в даному тесті. Ще більш дивним є те, що Vapor став другим, будучи єдиним фреймворком, окрім Spring, використовують ORM.

image

Код і конфігурація

Ви можете подивитися код тестових запитів і конфігурацію для кожного з фреймворків

Vapor

Vapor був запущений на POSIX-thread HTTP, який компилировался використовуючи swift's 06-06 toolchain з релизной конфігурацією і оптимізацією

vapor run --release --port=8000


Vapor CLI зробив створення і запуск додатків на цьому фреймворку дуже простим, це весь код, який ми використовували для тесту.

main.swift
import Vapor
import Fluent
import FluentSQLite

let app = Application()

do {
let driver = try SQLiteDriver(path: "/home/helper/database/test.sqlite")
Database.default = Database(driver: driver)
} catch {
print("Could not open SQLite database: \(error)")
}

app.get("plaintext") { request in
return "Hello, world!"
}

app.get("json") { request in
return JSON([
"array": [1, 2, 3],
"dict": ["one": 1, "two": 2, "three": 3],
"int": 42,
"string": "test",
"double": 3.14,
"null": nil
])
}

app.get("sqlite-fetch") { request in
guard let user = try User.random() else {
throw Abort.notFound
}

return user
}

app.globalMiddleware = []

app.start()



Налаштування бази даних виявилася досить простий з використанням Fluent, а Swift забезпечує вашому додатку захист від збоїв, навіть якщо база даних не там, де ми думаємо.

Ruby

«Рейки» були запущені за допомогою доданого сервера, бази даних і маршрути згенеровані у вигляді окремих файлів.

bin/rails s — binding=107.170.131.198 -p 8600 -e production

benchmark_controller.rb
class BenchmarkController < ActionController::Base
def plaintext
render plain: "Hello world"
end

def json
a = [1, 2, 3]
d = {"one" => 1, "two" => 2, "three" => 3}

r = {"array" => a, "dict" => d, "int" => 42, "string" => "test", "double" => 3.14, "null" => nil}

render :json => r
end

def sqlite
r = ActiveRecord::Base.connection.exec_query("SELECT * FROM users ORDER BY random() LIMIT 1")
render :json => r
end
end


database.yml
production:
<<: *default
database: /home/helper/database/test.sqlite


routes.rb
Rails.application.routes.draw do
get 'plaintext' => 'benchmark#plaintext'
get 'json' => 'benchmark#json'
get 'sqlite-fetch' => 'benchmark#sqlite'
end



Nancy

Nancy є open-source проектом .NET, переваги у нього просте тестування, легкий вага і розширюваність. Маючи більш ніж 250 співавторів і активна спільнота, Nancy показує наскільки C# може бути хороший в вебі.

HomeModule.cs
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using Dapper;
using Microsoft.Data.Sqlite;
using Nancy;

namespace NancyVsVapor
{
public class HomeModule : NancyModule
{
private static string connstring = string.Concat("Data Source=", Path.Combine(
Path.GetDirectoryName(typeof(HomeModule).GetTypeInfo().Assembly.Location),
"test.sqlite"));

private static Random random = new Random();

public HomeModule()
{
Get("/plaintext", _ => "Hello, World!");

Get("/json", _ =>
{
return Response.AsJson(new JsonModel());
});

Get("sqlite-fetch", async (_, __) =>
{
using (var conn = new SqliteConnection(connstring))
{
var users = await conn.QueryAsync<User>("select * from users where id = @id", new { id = random.Next(1, 3) });
return Response.AsJson(users.FirstOrDefault());
}
});
}
}
}



Laravel

Laravel був організований використовуючи Nginx і PHP 5.

laravel.conf
server {
listen 8701;

root /home/helper/laravel-tanner/benchmark/public;
index index.php index.html index.htm;

server_name 107.170.131.198;

location / {
try_files $uri $uri/ /index.php?$query_string;
}

location ~ \.php$ {
try_files $uri /index.php =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}


routes.php
<?php
use DB;
Route::get('/plaintext', function() {
return 'Hello, world!';
});
Route::get('/json', function() {
return [
'array' => [1, 2, 3],
'dict' => [
'one' => 1,
'two' => 2,
'three' => 3
],
'int' => 42,
'string' => 'test',
'double' => 3.14,
'null' => null
];
});
Route::get('/sqlite-fetch', function() {
return DB::select('SELECT * FROM users ORDER BY random() LIMIT 1');
});



Lumen

Lumen був організований аналогічно Laravel.

routes.php
<?php
$app->get('/plaintext', function() {
return 'Hello, world!';
});
$app->get('/json', function() {
return [
'array' => [1, 2, 3],
'dict' => [
'one' => 1,
'two' => 2,
'three' => 3
],
'int' => 42,
'string' => 'test',
'double' => 3.14,
'null' => null
];
});
$app->get('/sqlite-fetch', function() {
return \DB::select('SELECT * FROM users ORDER BY random() LIMIT 1');
});



Express

Express був запущений використовуючи NPM і кластер.

npm run cluster

cluster.js
var cluster = require('cluster');

if(cluster.isMaster) {
var cpuCount = require('os').cpus().length;

for(var i = 0; i < cpuCount; i += 1) {
cluster.fork();
}

cluster.on('exit', function(worker) {
console.log('Worker %d died, replacing', worker.id);
cluster.fork();
});
} else {
var app = require('./app.js');

app.app.listen(app.port, function() {
console.log('Benchmarking worker %d listening on %d', cluster.worker.id app.port)
});
}


app.js
const express = require('express');
const app = express();

const sqlite3 = require('sqlite3');
const db = new sqlite3.Database('../database/test.sqlite');

app.get('/plaintext', function(req, res) {
res.setHeader('Content-Type', 'text/plain');
res.send('Hello, World!');
});

app.get('/json', function(req, res) {
res.send({
array: [1, 2, 3],
dict: {
one: 1,
two: 2,
'three': 3
},
int: 42,
string: 'test',
double: 3.14,
'null': null
});
});

app.get('/sqlite-fetch', function(req, res) {
db.get('select * from users where id = ?', Math.floor(Math.random() * 3) + 1, function(err, row) {
if(err) {
res.send(err.message);
} else {
res.send(row);
}
});
});

module.exports = {
app: app,
port: 8400
}



Express отримує таку швидкість із-за того, що під капотом у нього знаходиться високопродуктивний C, навіть якщо ви пишете на JavaScript запити обробляються використовуючи C бібліотеки.

Django

Django був запущений використовуючи wsgi і gunicorn.

urls.py
from django.conf.urls import url
from django.http import HttpResponse
from django.http import JsonResponse
from django.db import connection

def plaintext(request):
return HttpResponse('Hello, world)

def json(request):
return JsonResponse({
"array": [1, 2, 3],
"dict": {"one": 1, "two": 2, "three": 3},
"int": 42,
"string": "test",
"double": 3.14,
"null": None
})

def sqlite(request):
cursor = connection.cursor()
cursor.execute("SELECT * FROM users ORDER BY random() LIMIT 1")
row = cursor.fetchone()

return JsonResponse(row, safe=False)

urlpatterns = [
url(r'^plaintext', plaintext),
url(r'^json', json),
url(r'^sqlite-fetch', sqlite)
]



Flask

До Flask той же підхід.

run.py
import sys
import flask
import random
import sqlite3
import logging
import socket

logging.basicConfig(level=logging.WARNING format='%(levelname)s: %(message)s')

app = flask.Flask(__name__)

application = app

db = sqlite3.connect('./test.sqlite')
conn = db.cursor()
conn.row_factory = sqlite3.Row

@app.route("/plaintext")
def plaintext():
return "Hello, world!"

@app.route("/json")
def json():
return flask.jsonify(**{
"array": [1, 2, 3],
"dict": {"one": 1, "two": 2, "three": 3},
"int": 42,
"string": "test",
"double": 3.14,
"null": None
})

@app.route("/sqlite-fetch")
def sqlite_fetch():
id = random.randint(1, 3)
r = conn.execute("select * from users where id = ?", (id)).fetchone()
if r is not None:
d = dict(zip(r.keys(), r))
return flask.jsonify(d)
else:
flask.abort(404)


if __name__ == "__main__":
port = 8137
print 'Listening on port %s' % port
while True:
try:
app.run(port=port, host="107.170.131.198")
sys.exit(0)
except socket.error as e:
logging.warn("socket error: %s" % e)



Go

Go використовує веб-сервер, маршрутизатор, а вся програма вмістилася в одному файлі.

bench.go
package main

import (
"encoding/json"
"flag"
"fmt"
"io"
"log"
"net/http"

"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
)

func Plaintext(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "Hello World!\n")
}

type JSONStruct struct {
Array []int `json:"array"`
Dict map[string]int `json:"dict"`
Int int `json:"int"`
String string `json:"string"`
Double float64 `json:"double"`
Null interface{} `json:"null"`
}

func JSON(w http.ResponseWriter, req *http.Request) {
j := JSONStruct{Array: [] (int) {1, 2, 3},
Dict: map[string]int{"one": 1, "two": 2, "three": 3},
Int: 42,
String: "test",
Double: 3.14,
Null: nil}

b, _ := json.MarshalIndent(j, "", " ")
io.WriteString(w, string(b))
}

type User struct {
ID int `db:"id" json:"id,omitempty"`
String Name `db:"name" json:"name,omitempty"`
Email string `db:"email" json:"email,omitempty"`
}

// typical usage would keep or cache the open DB connection
var db, _ = sqlx.Open("sqlite3", "../database/test.sqlite")

func SQLiteFetch(w http.ResponseWriter, req *http.Request) {
user := User{}
rows, err := db.Queryx("select * from users order by random() limit 1")
if err != nil {
log.Fatal(err)
}
defer rows.Close()

for rows.Next() {
err = rows.StructScan(&user)
if err != nil {
log.Fatal(err)
}

b, _ := json.MarshalIndent(user, "", " ")
io.WriteString(w, string(b))
}
}

var portNumber int

func main() {
flag.IntVar(&portNumber, "порту", 8300, "port number to listen on")
flag.Parse()

http.HandleFunc("/plaintext", Plaintext)
http.HandleFunc("/json", JSON)
http.HandleFunc("/sqlite-fetch", SQLiteFetch)

log.Println("bench running on", fmt.Sprintf("%d", portNumber))

err := http.ListenAndServe(fmt.Sprintf(":%d", portNumber), nil)
if err != nil {
log.Fatal(err)
}
}



Spring

Java була запущена за допомогою Spring Boot на JVM.

ApplicationController.java
package com.hlprmnky.vapor_spring_benchmark;

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;

import com.google.common.collect.ImmutableMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ApplicationController {

private final AtomicLong counter = new AtomicLong();
private final Random random = new Random();

@Autowired
private UserRepository userRepository;

@RequestMapping("/json")
public Json json() {
return new Json(counter.incrementAndGet(), Arrays.asList(1, 2, 3),
ImmutableMap.of("one", 1, "two", 2, "three", 3),
"test", 42, 3.14);
}

@RequestMapping("/plaintext")
public String plaintext() {
return "Hello, World!";
}

@RequestMapping("/sqlite-fetch")
public User sqliteFetch() {
List<User> allUsers = userRepository.findAll();
return allUsers.get(random.nextInt(allUsers.size()));
}
}



Спасибі за увагу, джерело ссылке.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.