Скорость работы 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 Droplet.

wrk -d 10 -t 4 -c 128 http://<host>:<port>/plaintext wrk -d 10 -t 4 -c 128 http://<host>:<port>/json wrk -d 10 -t 4 -c 128 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"`         Name  string `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, "port", 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()));     } } 

Спасибо за внимание, источник по ссылке.
ссылка на оригинал статьи https://habrahabr.ru/post/317006/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *