Selepas salah satu temu duga kerja terbaru saya, saya terkejut apabila menyedari bahawa syarikat yang saya mohon masih menggunakan Laravel, rangka kerja PHP yang saya cuba kira-kira sedekad yang lalu. Ia adalah baik pada masa itu, tetapi jika ada yang tetap dalam teknologi dan fesyen, ia adalah perubahan berterusan dan penampilan semula gaya dan konsep. Jika anda seorang pengaturcara JavaScript, anda mungkin biasa dengan jenaka lama ini
Pengaturcara 1: "Saya tidak suka rangka kerja JavaScript baharu ini!"
Pengaturcara 2: "Tidak perlu risau. Hanya tunggu enam bulan dan akan ada satu lagi untuk menggantikannya!"
Kerana ingin tahu, saya memutuskan untuk melihat dengan tepat apa yang berlaku apabila kita menguji lama dan baharu. Sudah tentu, web dipenuhi dengan tanda aras dan tuntutan, yang mana yang paling popular mungkin adalah Penanda Aras Rangka Kerja Web TechEmpower di sini . Kami tidak akan melakukan apa-apa yang hampir rumit seperti mereka hari ini. Kami akan memastikan perkara yang baik dan mudah kedua-duanya supaya artikel ini tidak akan berubah menjadi Perang dan Keamanan , dan bahawa anda akan mempunyai sedikit peluang untuk berjaga apabila anda selesai membaca. Kaveat biasa dikenakan: ini mungkin tidak berfungsi sama pada mesin anda, versi perisian yang berbeza boleh menjejaskan prestasi dan kucing Schrรถdinger sebenarnya menjadi kucing zombi yang separuh hidup dan separuh mati pada masa yang sama.
Untuk ujian ini, saya akan menggunakan komputer riba saya bersenjatakan dengan i5 kecil yang menjalankan Manjaro Linux seperti yang ditunjukkan di sini.
โฐโโค uname -a
Linux jimsredmi 5.10.174-1-MANJARO #1 SMP PREEMPT Tuesday Mar 21 11:15:28 UTC 2023 x86_64 GNU/Linux
โฐโโค cat /proc/cpuinfo
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 126
model name : Intel(R) Core(TM) i5-1035G1 CPU @ 1.00GHz
stepping : 5
microcode : 0xb6
cpu MHz : 990.210
cache size : 6144 KB
Kod kami akan mempunyai tiga tugas mudah untuk setiap permintaan:
Apakah jenis ujian bodoh itu, anda mungkin bertanya? Nah, jika anda melihat permintaan rangkaian untuk halaman ini, anda akan melihat satu yang dipanggil sessionvars.js yang melakukan perkara yang sama.
Anda lihat, halaman web moden adalah makhluk yang rumit, dan salah satu tugas yang paling biasa ialah menyimpan halaman yang kompleks untuk mengelakkan beban berlebihan pada pelayan pangkalan data.
Jika kami memaparkan semula halaman yang kompleks setiap kali pengguna memintanya, maka kami hanya boleh melayani kira-kira 600 pengguna sesaat.
โฐโโค wrk -d 10s -t 4 -c 100 http://127.0.0.1/system/index.en.html
Running 10s test @ http://127.0.0.1/system/index.en.html
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 186.83ms 174.22ms 1.06s 81.16%
Req/Sec 166.11 58.84 414.00 71.89%
6213 requests in 10.02s, 49.35MB read
Requests/sec: 619.97
Transfer/sec: 4.92MB
Tetapi jika kami menyimpan halaman ini sebagai fail HTML statik dan membiarkan Nginx melemparkannya ke luar tetingkap dengan cepat kepada pengguna, maka kami boleh melayani 32,000 pengguna sesaat, meningkatkan prestasi sebanyak 50x ganda.
โฐโโค wrk -d 10s -t 4 -c 100 http://127.0.0.1/system/index.en.html
Running 10s test @ http://127.0.0.1/system/index.en.html
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 3.03ms 511.95us 6.87ms 68.10%
Req/Sec 8.20k 1.15k 28.55k 97.26%
327353 requests in 10.10s, 2.36GB read
Requests/sec: 32410.83
Transfer/sec: 238.99MB
index.en.html statik ialah bahagian yang pergi kepada semua orang dan hanya bahagian yang berbeza mengikut pengguna dihantar dalam sessionvars.js. Ini bukan sahaja mengurangkan beban pangkalan data dan mencipta pengalaman yang lebih baik untuk pengguna kami, tetapi juga mengurangkan kebarangkalian kuantum bahawa pelayan kami akan mengewap secara spontan dalam pelanggaran teras meledingkan apabila Klingons menyerang.
Kod yang dikembalikan untuk setiap rangka kerja akan mempunyai satu keperluan mudah: tunjukkan kepada pengguna berapa kali mereka telah memuat semula halaman dengan menyebut "Kiraan ialah x". Untuk memastikan perkara mudah, kami akan menjauhkan diri daripada baris gilir Redis, komponen Kubernetes atau AWS Lambdas buat masa ini.
Data sesi setiap pengguna akan disimpan dalam pangkalan data PostgreSQL.
Dan jadual pangkalan data ini akan dipotong sebelum setiap ujian.
Mudah tetapi berkesan ialah moto Pafera... di luar garis masa paling gelap pula...
Okay, jadi sekarang kita akhirnya boleh mula mengotorkan tangan kita. Kami akan melangkau persediaan untuk Laravel kerana ia hanya sekumpulan komposer dan artisan arahan.
Mula-mula, kami akan menyediakan tetapan pangkalan data kami dalam fail .env
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=sessiontest
DB_USERNAME=sessiontest
DB_PASSWORD=sessiontest
Kemudian kami akan menetapkan satu laluan sandaran tunggal yang menghantar setiap permintaan kepada pengawal kami.
Route::fallback(SessionController::class);
Dan tetapkan pengawal untuk memaparkan kiraan. Laravel, secara lalai, menyimpan sesi dalam pangkalan data. Ia juga menyediakan session()
berfungsi untuk antara muka dengan data sesi kami, jadi yang diperlukan hanyalah beberapa baris kod untuk memaparkan halaman kami.
class SessionController extends Controller
{
public function __invoke(Request $request)
{
$count = session('count', 0);
$count += 1;
session(['count' => $count]);
return 'Count is ' . $count;
}
}
Selepas menyediakan php-fpm dan Nginx, halaman kami kelihatan agak bagus...
โฐโโค php -v
PHP 8.2.2 (cli) (built: Feb 1 2023 08:33:04) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.2, Copyright (c) Zend Technologies
with Xdebug v3.2.0, Copyright (c) 2002-2022, by Derick Rethans
โฐโโค sudo systemctl restart php-fpm
โฐโโค sudo systemctl restart nginx
Sekurang-kurangnya sehingga kita benar-benar melihat keputusan ujian...
PHP/Laravel
โฐโโค wrk -d 10s -t 4 -c 100 http://127.0.0.1
Running 10s test @ http://127.0.0.1
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.08s 546.33ms 1.96s 65.71%
Req/Sec 12.37 7.28 40.00 56.64%
211 requests in 10.03s, 177.21KB read
Socket errors: connect 0, read 0, write 0, timeout 176
Requests/sec: 21.04
Transfer/sec: 17.67KB
Tidak, itu bukan kesilapan menaip. Mesin ujian kami telah berubah daripada 600 permintaan sesaat yang memaparkan halaman yang kompleks... kepada 21 permintaan sesaat pemaparan "Kiraan ialah 1".
Jadi apa yang salah? Adakah sesuatu yang salah dengan pemasangan PHP kami? Adakah Nginx entah bagaimana menjadi perlahan apabila antara muka dengan php-fpm?
Mari buat semula halaman ini dalam kod PHP tulen.
<?php
// ====================================================================
function uuid4()
{
return sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
}
// ====================================================================
function Query($db, $query, $params = [])
{
$s = $db->prepare($query);
$s->setFetchMode(PDO::FETCH_ASSOC);
$s->execute(array_values($params));
return $s;
}
// ********************************************************************
session_start();
$sessionid = 0;
if (isset($_SESSION['sessionid']))
{
$sessionid = $_SESSION['sessionid'];
}
if (!$sessionid)
{
$sessionid = uuid4();
$_SESSION['sessionid'] = $sessionid;
}
$db = new PDO('pgsql:host=127.0.0.1 dbname=sessiontest user=sessiontest password=sessiontest');
$data = 0;
try
{
$result = Query(
$db,
'SELECT data FROM usersessions WHERE uid = ?',
[$sessionid]
)->fetchAll();
if ($result)
{
$data = json_decode($result[0]['data'], 1);
}
} catch (Exception $e)
{
echo $e;
Query(
$db,
'CREATE TABLE usersessions(
uid TEXT PRIMARY KEY,
data TEXT
)'
);
}
if (!$data)
{
$data = ['count' => 0];
}
$data['count']++;
if ($data['count'] == 1)
{
Query(
$db,
'INSERT INTO usersessions(uid, data)
VALUES(?, ?)',
[$sessionid, json_encode($data)]
);
} else
{
Query(
$db,
'UPDATE usersessions
SET data = ?
WHERE uid = ?',
[json_encode($data), $sessionid]
);
}
echo 'Count is ' . $data['count'];
Kami kini telah menggunakan 98 baris kod untuk melakukan apa yang dilakukan oleh empat baris kod (dan sekumpulan keseluruhan kerja konfigurasi) dalam Laravel. (Sudah tentu, jika kami melakukan pengendalian ralat yang betul dan mesej yang dihadapi pengguna, ini akan menjadi kira-kira dua kali ganda bilangan baris.) Mungkin kita boleh mencapai 30 permintaan sesaat?
PHP/Pure PHP
โฐโโค wrk -d 10s -t 4 -c 100 http://127.0.0.1
Running 10s test @ http://127.0.0.1
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 140.79ms 27.88ms 332.31ms 90.75%
Req/Sec 178.63 58.34 252.00 61.01%
7074 requests in 10.04s, 3.62MB read
Requests/sec: 704.46
Transfer/sec: 369.43KB
Whoa! Nampaknya tiada apa yang salah dengan pemasangan PHP kami. Versi PHP tulen melakukan 700 permintaan sesaat.
Jika tiada apa-apa yang salah dengan PHP, mungkin kami salah konfigurasi Laravel?
Selepas menyelusuri web untuk isu konfigurasi dan petua prestasi, dua daripada teknik yang paling popular adalah untuk cache data konfigurasi dan laluan untuk mengelak daripada memprosesnya untuk setiap permintaan. Oleh itu, kami akan mengambil nasihat mereka dan mencuba petua ini.
โฐโโค php artisan config:cache
INFO Configuration cached successfully.
โฐโโค php artisan route:cache
INFO Routes cached successfully.
Semuanya kelihatan baik pada baris arahan. Mari buat semula penanda aras.
โฐโโค wrk -d 10s -t 4 -c 100 http://127.0.0.1
Running 10s test @ http://127.0.0.1
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.13s 543.50ms 1.98s 61.90%
Req/Sec 25.45 13.39 50.00 55.77%
289 requests in 10.04s, 242.15KB read
Socket errors: connect 0, read 0, write 0, timeout 247
Requests/sec: 28.80
Transfer/sec: 24.13KB
Nah, kami kini telah meningkatkan prestasi daripada 21.04 kepada 28.80 permintaan sesaat, peningkatan dramatik hampir 37%! Ini agak mengagumkan untuk mana-mana pakej perisian... kecuali fakta bahawa kami masih hanya melakukan 1/24hb daripada bilangan permintaan versi PHP tulen.
Jika anda berfikir bahawa ada sesuatu yang tidak kena dengan ujian ini, anda harus bercakap dengan pengarang rangka kerja PHP Lucinda. Dalam keputusan ujiannya, dia telah Lucinda menewaskan Laravel sebanyak 36x untuk permintaan HTML dan 90x untuk permintaan JSON.
Selepas menguji pada mesin saya sendiri dengan Apache dan Nginx, saya tidak mempunyai sebab untuk meraguinya. Laravel benar-benar adil itu lambat! PHP dengan sendirinya tidak begitu buruk, tetapi setelah anda menambah semua pemprosesan tambahan yang Laravel tambahkan pada setiap permintaan, maka saya merasa sangat sukar untuk mengesyorkan Laravel sebagai pilihan pada tahun 2023.
Akaun PHP/Wordpress untuk kira-kira 40% daripada semua tapak web di web , menjadikannya rangka kerja yang paling dominan. Secara peribadi, saya mendapati bahawa populariti tidak semestinya diterjemahkan kepada kualiti lebih daripada saya mendapati diri saya mempunyai keinginan yang tidak terkawal secara tiba-tiba untuk makanan gourmet yang luar biasa itu daripada restoran paling popular di dunia ... McDonald's. Memandangkan kami telah pun menguji kod PHP tulen, kami tidak akan menguji Wordpress itu sendiri, kerana apa-apa yang melibatkan Wordpress sudah pasti lebih rendah daripada 700 permintaan sesaat yang kami perhatikan dengan PHP tulen.
Django ialah satu lagi rangka kerja popular yang telah wujud sejak sekian lama. Jika anda pernah menggunakannya pada masa lalu, anda mungkin teringat dengan antara muka pentadbiran pangkalan datanya yang menakjubkan bersama-sama dengan betapa menjengkelkannya untuk mengkonfigurasi segala-galanya mengikut cara yang anda mahukan. Mari lihat sejauh mana Django berfungsi pada tahun 2023, terutamanya dengan antara muka ASGI baharu yang telah ditambahkan pada versi 4.0.
Menyediakan Django sangat serupa dengan menyediakan Laravel, kerana kedua-duanya dari zaman seni bina MVC bergaya dan betul. Kami akan melangkau konfigurasi yang membosankan dan pergi terus ke menyediakan paparan.
from django.shortcuts import render
from django.http import HttpResponse
# =====================================================================
def index(request):
count = request.session.get('count', 0)
count += 1
request.session['count'] = count
return HttpResponse(f"Count is {count}")
Empat baris kod adalah sama dengan versi Laravel. Mari lihat prestasinya.
โฐโโค python --version
Python 3.10.9
Python/Django
โฐโโค gunicorn --access-logfile - -k uvicorn.workers.UvicornWorker -w 4 djangotest.asgi
[2023-03-21 15:20:38 +0800] [2886633] [INFO] Starting gunicorn 20.1.0
โฐโโค wrk -d 10s -t 4 -c 100 http://127.0.0.1:8000/sessiontest/
Running 10s test @ http://127.0.0.1:8000/sessiontest/
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 277.71ms 142.84ms 835.12ms 69.93%
Req/Sec 91.21 57.57 230.00 61.04%
3577 requests in 10.06s, 1.46MB read
Requests/sec: 355.44
Transfer/sec: 148.56KB
Tidak buruk sama sekali pada 355 permintaan sesaat. Ia hanya separuh daripada prestasi versi PHP tulen, tetapi ia juga 12x ganda daripada versi Laravel. Django lwn. Laravel nampaknya bukan pertandingan sama sekali.
Selain daripada rangka kerja segala-galanya yang lebih besar-termasuk-singki-dapur, terdapat juga rangka kerja yang lebih kecil yang hanya melakukan beberapa persediaan asas sambil membenarkan anda mengendalikan yang lain. Salah satu yang terbaik untuk digunakan ialah Flask dan Quart rakan sejawatan ASGInya. saya sendiri Rangka Kerja PaferaPy dibina di atas Flask, jadi saya mengetahui betapa mudahnya untuk menyelesaikan sesuatu sambil mengekalkan prestasi.
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Session benchmark test
import json
import psycopg
import uuid
from flask import Flask, session, redirect, url_for, request, current_app, g, abort, send_from_directory
from flask.sessions import SecureCookieSessionInterface
app = Flask('pafera')
app.secret_key = b'secretkey'
dbconn = 0
# =====================================================================
@app.route('/', defaults={'path': ''}, methods = ['GET', 'POST'])
@app.route('/<path:path>', methods = ['GET', 'POST'])
def index(path):
"""Handles all requests for the server.
We route all requests through here to handle the database and session
logic in one place.
"""
global dbconn
if not dbconn:
dbconn = psycopg.connect('dbname=sessiontest user=sessiontest password=sessiontest')
cursor = dbconn.execute('''
CREATE TABLE IF NOT EXISTS usersessions(
uid TEXT PRIMARY KEY,
data TEXT
)
''')
cursor.close()
dbconn.commit()
sessionid = session.get('sessionid', 0)
if not sessionid:
sessionid = uuid.uuid4().hex
session['sessionid'] = sessionid
cursor = dbconn.execute("SELECT data FROM usersessions WHERE uid = %s", [sessionid])
row = cursor.fetchone()
count = json.loads(row[0])['count'] if row else 0
count += 1
newdata = json.dumps({'count': count})
if count == 1:
cursor.execute("""
INSERT INTO usersessions(uid, data)
VALUES(%s, %s)
""",
[sessionid, newdata]
)
else:
cursor.execute("""
UPDATE usersessions
SET data = %s
WHERE uid = %s
""",
[newdata, sessionid]
)
cursor.close()
dbconn.commit()
return f'Count is {count}'
Seperti yang anda lihat, skrip Flask adalah lebih pendek daripada skrip PHP tulen. Saya mendapati bahawa daripada semua bahasa yang saya gunakan, Python mungkin merupakan bahasa yang paling ekspresif dari segi ketukan kekunci yang ditaip. Kekurangan pendakap dan kurungan, senarai dan pemahaman imlak, dan penyekatan berdasarkan lekukan dan bukannya titik bertitik menjadikan Python agak mudah tetapi berkuasa dalam keupayaannya.
Malangnya, Python juga merupakan bahasa tujuan umum yang paling perlahan di luar sana, walaupun berapa banyak perisian telah ditulis di dalamnya. Bilangan perpustakaan Python yang tersedia adalah kira-kira empat kali lebih banyak daripada bahasa yang serupa dan meliputi sejumlah besar domain, namun tiada siapa yang akan mengatakan bahawa Python adalah pantas atau berprestasi di luar niche seperti NumPy.
Mari lihat bagaimana versi Flask kami dibandingkan dengan rangka kerja kami yang terdahulu.
Python/Flask
โฐโโค gunicorn --access-logfile - -w 4 flasksite:app
[2023-03-21 15:32:49 +0800] [2856296] [INFO] Starting gunicorn 20.1.0
โฐโโค wrk -d 10s -t 4 -c 100 http://127.0.0.1:8000
Running 10s test @ http://127.0.0.1:8000
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 91.84ms 11.97ms 149.63ms 86.18%
Req/Sec 272.04 39.05 380.00 74.50%
10842 requests in 10.04s, 3.27MB read
Requests/sec: 1080.28
Transfer/sec: 333.37KB
Skrip Flask kami sebenarnya lebih pantas daripada versi PHP tulen kami!
Jika anda terkejut dengan perkara ini, anda harus sedar bahawa aplikasi Flask kami melakukan semua permulaan dan konfigurasinya apabila kami memulakan pelayan gunicorn, manakala PHP melaksanakan semula skrip setiap kali permintaan baharu masuk. Ia' Setara dengan Flask sebagai pemandu teksi yang muda dan bersemangat yang sudah menghidupkan kereta dan menunggu di tepi jalan, manakala PHP ialah pemandu tua yang tinggal di rumahnya menunggu panggilan masuk dan baru memandu. untuk menjemput anda. Sebagai seorang lelaki sekolah lama dan datang dari zaman di mana PHP merupakan perubahan yang menarik kepada fail HTML dan SHTML biasa, agak menyedihkan untuk menyedari betapa banyak masa telah berlalu, tetapi perbezaan reka bentuk benar-benar menyukarkan PHP untuk bersaing dengan pelayan Python, Java, dan Node.js yang hanya kekal dalam ingatan dan mengendalikan permintaan dengan lincah seperti seorang juggler.
Flask mungkin rangka kerja terpantas kami setakat ini, tetapi ia sebenarnya perisian yang agak lama. Komuniti Python bertukar kepada pelayan ASGI tak segerak yang lebih baru beberapa tahun lalu, dan sudah tentu, saya sendiri telah bertukar bersama mereka.
Versi terbaru Rangka Kerja Pafera, PaferaPyAsync , adalah berdasarkan Starlette. Walaupun terdapat versi ASGI Flask yang dipanggil Quart, perbezaan prestasi antara Quart dan Starlette sudah cukup untuk saya meletakkan semula kod saya pada Starlette.
Pengaturcaraan asychronous boleh menakutkan ramai orang, tetapi ia sebenarnya bukan konsep yang sukar terima kasih kepada Node.js yang mempopularkan konsep itu sejak sedekad lalu.
Kami pernah melawan konkurensi dengan multithreading, multiprocessing, pengkomputeran teragih, janji rantai, dan semua masa yang menyeronokkan yang menua dan mengeringkan ramai pengaturcara veteran sebelum waktunya. Sekarang, kita hanya menaip async
di hadapan majlis kami dan await
di hadapan mana-mana kod yang mungkin mengambil sedikit masa untuk dilaksanakan. Ia sememangnya lebih bertele-tele daripada kod biasa, tetapi lebih tidak menjengkelkan untuk digunakan daripada perlu berurusan dengan primitif penyegerakan, penghantaran mesej dan menyelesaikan janji.
Fail Starlette kami kelihatan seperti ini:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Session benchmark test
import json
import uuid
import psycopg
from starlette.applications import Starlette
from starlette.responses import Response, PlainTextResponse, JSONResponse, RedirectResponse, HTMLResponse
from starlette.routing import Route, Mount, WebSocketRoute
from starlette_session import SessionMiddleware
dbconn = 0
# =====================================================================
async def index(R):
global dbconn
if not dbconn:
dbconn = await psycopg.AsyncConnection.connect('dbname=sessiontest user=sessiontest password=sessiontest')
cursor = await dbconn.execute('''
CREATE TABLE IF NOT EXISTS usersessions(
uid TEXT PRIMARY KEY,
data TEXT
)
''')
await cursor.close()
await dbconn.commit()
sessionid = R.session.get('sessionid', 0)
if not sessionid:
sessionid = uuid.uuid4().hex
R.session['sessionid'] = sessionid
cursor = await dbconn.execute("SELECT data FROM usersessions WHERE uid = %s", [sessionid])
row = await cursor.fetchone()
count = json.loads(row[0])['count'] if row else 0
count += 1
newdata = json.dumps({'count': count})
if count == 1:
await cursor.execute("""
INSERT INTO usersessions(uid, data)
VALUES(%s, %s)
""",
[sessionid, newdata]
)
else:
await cursor.execute("""
UPDATE usersessions
SET data = %s
WHERE uid = %s
""",
[newdata, sessionid]
)
await cursor.close()
await dbconn.commit()
return PlainTextResponse(f'Count is {count}')
# *********************************************************************
app = Starlette(
debug = True,
routes = [
Route('/{path:path}', index, methods = ['GET', 'POST']),
],
)
app.add_middleware(
SessionMiddleware,
secret_key = 'testsecretkey',
cookie_name = "pafera",
)
Seperti yang anda lihat, ia cukup banyak disalin dan ditampal daripada skrip Flask kami dengan hanya beberapa perubahan penghalaan dan async/await
kata kunci.
Berapa banyak peningkatan yang boleh memberi kita salin dan tampal kod?
Python/Starlette
โฐโโค gunicorn --access-logfile - -k uvicorn.workers.UvicornWorker -w 4 starlettesite:app 130 โต
[2023-03-21 15:42:34 +0800] [2856220] [INFO] Starting gunicorn 20.1.0
โฐโโค wrk -d 10s -t 4 -c 100 http://127.0.0.1:8000
Running 10s test @ http://127.0.0.1:8000
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 21.85ms 10.45ms 67.29ms 55.18%
Req/Sec 1.15k 170.11 1.52k 66.00%
45809 requests in 10.04s, 13.85MB read
Requests/sec: 4562.82
Transfer/sec: 1.38MB
Kita ada juara baru, tuan-tuan dan puan-puan! Nilai tertinggi kami sebelum ini ialah versi PHP tulen kami pada 704 permintaan sesaat, yang kemudiannya diatasi oleh versi Flask kami pada 1080 permintaan sesaat. Skrip Starlette kami menghancurkan semua pesaing sebelumnya pada 4562 permintaan sesaat, bermakna peningkatan 6x berbanding PHP tulen dan peningkatan 4x berbanding Flask.
Jika anda belum menukar kod WSGI Python anda kepada ASGI, sekarang mungkin masa yang baik untuk bermula.
Setakat ini, kami hanya meliputi rangka kerja PHP dan Python. Walau bagaimanapun, sebahagian besar dunia sebenarnya menggunakan Java, DotNet, Node.js, Ruby on Rails dan lain-lain teknologi sedemikian untuk tapak web mereka. Ini sama sekali bukan gambaran keseluruhan menyeluruh bagi semua ekosistem dan biom dunia, jadi untuk mengelak daripada melakukan pengaturcaraan yang setara dengan kimia organik, kami akan memilih hanya rangka kerja yang paling mudah untuk menaip kod. .
Melainkan anda telah bersembunyi di bawah salinan K&R C atau Knuth's anda Seni Pengaturcaraan Komputer selama lima belas tahun yang lalu, anda mungkin pernah mendengar tentang Node.js. Kami yang telah wujud sejak permulaan JavaScript sama ada sangat takut, kagum, atau kedua-duanya pada keadaan JavaScript moden, tetapi tidak dapat dinafikan bahawa JavaScript telah menjadi satu kuasa yang perlu diperhitungkan pada pelayan juga sebagai pelayar. Lagipun, kami juga mempunyai integer 64 bit asli sekarang dalam bahasa! Itu jauh lebih baik daripada semua yang disimpan dalam terapung 64 bit setakat ini!
ExpressJS mungkin merupakan pelayan Node.js yang paling mudah digunakan, jadi kami akan melakukan aplikasi Node.js/ExpressJS yang cepat dan kotor untuk menyediakan kaunter kami.
/**********************************************************************
* Simple session test using ExpressJS.
**********************************************************************/
var L = console.log;
var uuid = require('uuid4');
var express = require('express');
var session = require('express-session');
var MemoryStore = require('memorystore')(session);
var { Client } = require('pg')
var db = 0;
var app = express();
const PORT = 8000;
//session middleware
app.use(
session({
secret: "secretkey",
saveUninitialized: true,
resave: false,
store: new MemoryStore({
checkPeriod: 1000 * 60 * 60 * 24 // prune expired entries every 24h
})
})
);
app.get('/',
async function(req,res)
{
if (!db)
{
db = new Client({
user: 'sessiontest',
host: '127.0.0.1',
database: 'sessiontest',
password: 'sessiontest'
});
await db.connect();
await db.query(`
CREATE TABLE IF NOT EXISTS usersessions(
uid TEXT PRIMARY KEY,
data TEXT
)`,
[]
);
};
var session = req.session;
if (!session.sessionid)
{
session.sessionid = uuid();
}
var row = 0;
let queryresult = await db.query(`
SELECT data::TEXT
FROM usersessions
WHERE uid = $1`,
[session.sessionid]
);
if (queryresult && queryresult.rows.length)
{
row = queryresult.rows[0].data;
}
var count = 0;
if (row)
{
var data = JSON.parse(row);
data.count += 1;
count = data.count;
await db.query(`
UPDATE usersessions
SET data = $1
WHERE uid = $2
`,
[JSON.stringify(data), session.sessionid]
);
} else
{
await db.query(`
INSERT INTO usersessions(uid, data)
VALUES($1, $2)`,
[session.sessionid, JSON.stringify({count: 1})]
);
count = 1;
}
res.send(`Count is ${count}`);
}
);
app.listen(PORT, () => console.log(`Server Running at port ${PORT}`));
Kod ini sebenarnya lebih mudah untuk ditulis berbanding versi Python, walaupun JavaScript asli menjadi agak sukar apabila aplikasi menjadi lebih besar, dan semua percubaan untuk membetulkannya seperti TypeScript dengan cepat menjadi lebih bertele-tele daripada Python.
Mari lihat prestasinya!
Node.js/ExpressJS
โฐโโค node --version v19.6.0
โฐโโค NODE_ENV=production node nodejsapp.js 130 โต
Server Running at port 8000
โฐโโค wrk -d 10s -t 4 -c 100 http://127.0.0.1:8000
Running 10s test @ http://127.0.0.1:8000
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 90.41ms 7.20ms 188.29ms 85.16%
Req/Sec 277.15 37.21 393.00 81.66%
11018 requests in 10.02s, 3.82MB read
Requests/sec: 1100.12
Transfer/sec: 390.68KB
Anda mungkin pernah mendengar cerita rakyat kuno (kuno mengikut piawaian Internet...) tentang Node.js' kelajuan, dan cerita tersebut kebanyakannya benar terima kasih kepada kerja hebat yang telah Google lakukan dengan enjin JavaScript V8. Dalam kes ini, walaupun apl pantas kami mengatasi skrip Flask, sifat berulir tunggalnya dikalahkan oleh empat proses async yang digunakan oleh Starlette Knight yang berkata "Ni!".
Mari dapatkan bantuan lagi!
โฐโโค pm2 start nodejsapp.js -i 4
[PM2] Spawning PM2 daemon with pm2_home=/home/jim/.pm2
[PM2] PM2 Successfully daemonized
[PM2] Starting /home/jim/projects/paferarust/nodejsapp.js in cluster_mode (4 instances)
[PM2] Done.
โโโโโโฌโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโฌโโโโโโโโโโฌโโโโโโโโโโฌโโโโโโโโโโโฌโโโโโโโโโฌโโโโโโโฌโโโโโโโโโโโโฌโโโโโโโโโโโฌโโโโโโโโโโโฌโโโโโโโโโโโฌโโโโโโโโโโโ
โ id โ name โ namespace โ version โ mode โ pid โ uptime โ โบ โ status โ cpu โ mem โ user โ watching โ
โโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโโผโโโโโโโโโผโโโโโโโผโโโโโโโโโโโโผโโโโโโโโโโโผโโโโโโโโโโโผโโโโโโโโโโโผโโโโโโโโโโโค
โ 0 โ nodejsapp โ default โ N/A โ cluster โ 37141 โ 0s โ 0 โ online โ 0% โ 64.6mb โ jim โ disabled โ
โ 1 โ nodejsapp โ default โ N/A โ cluster โ 37148 โ 0s โ 0 โ online โ 0% โ 64.5mb โ jim โ disabled โ
โ 2 โ nodejsapp โ default โ N/A โ cluster โ 37159 โ 0s โ 0 โ online โ 0% โ 56.0mb โ jim โ disabled โ
โ 3 โ nodejsapp โ default โ N/A โ cluster โ 37171 โ 0s โ 0 โ online โ 0% โ 45.3mb โ jim โ disabled โ
โโโโโโดโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโดโโโโโโโโโโดโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโดโโโโโโโดโโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโโโ
Okey! Kini ia adalah pertarungan genap empat lawan empat! Mari penanda aras!
โฐโโค wrk -d 10s -t 4 -c 100 http://127.0.0.1:8000
Running 10s test @ http://127.0.0.1:8000
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 45.09ms 19.89ms 176.14ms 60.22%
Req/Sec 558.93 97.50 770.00 66.17%
22234 requests in 10.02s, 7.71MB read
Requests/sec: 2218.69
Transfer/sec: 787.89KB
Masih tidak cukup pada tahap Starlette, tetapi ia tidak buruk untuk penggodaman JavaScript lima minit pantas. Daripada ujian saya sendiri, skrip ini sebenarnya ditahan sedikit di peringkat antara muka pangkalan data kerana nod-postgres tidak begitu cekap seperti psycopg untuk Python. Beralih kepada sqlite kerana pemacu pangkalan data menghasilkan lebih 3000 permintaan sesaat untuk kod ExpressJS yang sama.
Perkara utama yang perlu diberi perhatian ialah walaupun kelajuan pelaksanaan Python yang perlahan, rangka kerja ASGI sebenarnya boleh bersaing dengan penyelesaian Node.js untuk beban kerja tertentu.
Jadi sekarang, kita semakin hampir ke puncak gunung, dan mengikut gunung, saya maksudkan markah penanda aras tertinggi yang direkodkan oleh tikus dan lelaki.
Jika anda melihat kebanyakan penanda aras rangka kerja yang tersedia di web, anda akan dapati bahawa terdapat dua bahasa yang cenderung mendominasi bahagian atas: C++ dan Rust. Saya telah bekerja dengan C++ sejak tahun 90-an, dan saya juga mempunyai rangka kerja Win32 C++ saya sendiri sebelum MFC/ATL digunakan, jadi saya mempunyai banyak pengalaman dengan bahasa itu. Tidak menyeronokkan untuk bekerja dengan sesuatu apabila anda sudah mengetahuinya, jadi kami akan melakukan versi Rust sebaliknya. ;)
Rust agak baru setakat bahasa pengaturcaraan, tetapi ia menjadi objek rasa ingin tahu bagi saya apabila Linus Torvalds mengumumkan bahawa dia akan menerima Rust sebagai bahasa pengaturcaraan kernel Linux. Bagi kami pengaturcara yang lebih tua, itu hampir sama dengan mengatakan bahawa gaya hippie zaman baharu yang bergelar baharu ini akan menjadi pindaan baharu kepada Perlembagaan A.S..
Kini, apabila anda seorang pengaturcara yang berpengalaman, anda cenderung untuk tidak mengikuti kereta muzik secepat orang muda, atau anda mungkin terbakar oleh perubahan pantas pada bahasa atau perpustakaan. (Sesiapa sahaja yang menggunakan versi pertama AngularJS akan tahu apa yang saya bincangkan.) Rust masih agak dalam peringkat pembangunan percubaan itu, dan saya rasa lucu bahawa banyak contoh kod di web tidak susun lagi dengan versi pakej semasa.
Walau bagaimanapun, prestasi yang ditunjukkan oleh aplikasi Rust tidak boleh dinafikan. Jika anda tidak pernah mencuba ripgrep atau fd-cari keluar pada pokok kod sumber yang besar, anda pasti perlu memberi mereka putaran. Mereka juga tersedia untuk kebanyakan pengedaran Linux hanya daripada pengurus pakej. Anda sedang menukar verbosity untuk persembahan dengan Rust... a banyak verbosity untuk a banyak prestasi.
Kod lengkap untuk Rust agak besar, jadi kami hanya akan melihat pada pengendali yang berkaitan di sini:
// =====================================================================
pub async fn RunQuery(
db: &web::Data<Pool>,
query: &str,
args: &[&(dyn ToSql + Sync)]
) -> Result<Vec<tokio_postgres::row::Row>, tokio_postgres::Error>
{
let client = db.get().await.unwrap();
let statement = client.prepare_cached(query).await.unwrap();
client.query(&statement, args).await
}
// =====================================================================
pub async fn index(
req: HttpRequest,
session: Session,
db: web::Data<Pool>,
) -> Result<HttpResponse, Error>
{
let mut count = 1;
if let Some(sessionid) = session.get::<String>("sessionid")?
{
let rows = RunQuery(
&db,
"SELECT data
FROM usersessions
WHERE uid = $1",
&[&sessionid]
).await.unwrap();
if rows.is_empty()
{
let jsondata = serde_json::json!({
"count": 1,
}).to_string();
RunQuery(
&db,
"INSERT INTO usersessions(uid, data)
VALUES($1, $2)",
&[&sessionid, &jsondata]
).await
.expect("Insert failed!");
} else
{
let jsonstring:&str = rows[0].get(0);
let countdata: CountData = serde_json::from_str(jsonstring)?;
count = countdata.count;
count += 1;
let jsondata = serde_json::json!({
"count": count,
}).to_string();
RunQuery(
&db,
"UPDATE usersessions
SET data = $1
WHERE uid = $2
",
&[&jsondata, &sessionid]
).await
.expect("Update failed!");
}
} else
{
let sessionid = Uuid::new_v4().to_string();
let jsondata = serde_json::json!({
"count": 1,
}).to_string();
RunQuery(
&db,
"INSERT INTO usersessions(uid, data)
VALUES($1, $2)",
&[&sessionid, &jsondata]
).await
.expect("Insert failed!");
session.insert("sessionid", sessionid)?;
}
Ok(HttpResponse::Ok().body(format!(
"Count is {:?}",
count
)))
}
Ini jauh lebih rumit daripada versi Python/Node.js...
Rust/Actix
โฐโโค cargo run --release
[2023-03-21T23:37:25Z INFO actix_server::builder] starting 4 workers
Server running at http://127.0.0.1:8888/
โฐโโค wrk -d 10s -t 4 -c 100 http://127.0.0.1:8888
Running 10s test @ http://127.0.0.1:8888
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 9.93ms 3.90ms 77.18ms 94.87%
Req/Sec 2.59k 226.41 2.83k 89.25%
102951 requests in 10.03s, 24.59MB read
Requests/sec: 10267.39
Transfer/sec: 2.45MB
Dan banyak lagi yang berprestasi!
Pelayan Rust kami menggunakan Actix/deadpool_postgres dengan mudah mengalahkan juara kami sebelum ini Starlette sebanyak +125%, ExpressJS sebanyak +362% dan PHP tulen sebanyak +1366%. (Saya akan meninggalkan delta prestasi dengan versi Laravel sebagai latihan untuk pembaca.)
Saya mendapati bahawa mempelajari bahasa Rust itu sendiri adalah lebih sukar daripada bahasa lain kerana ia mempunyai lebih banyak gotcha daripada apa-apa yang saya lihat di luar 6502 Assembly, tetapi jika pelayan Rust anda boleh mengambil 14x bilangan pengguna sebagai pelayan PHP anda, maka mungkin ada sesuatu yang boleh diperolehi dengan menukar teknologi. Itulah sebabnya versi Rangka Kerja Pafera yang seterusnya akan berasaskan Rust. Keluk pembelajaran jauh lebih tinggi daripada bahasa skrip, tetapi prestasinya akan berbaloi. Jika anda tidak boleh meluangkan masa untuk mempelajari Rust, maka mengasaskan timbunan teknologi anda pada Starlette atau Node.js bukanlah keputusan yang buruk juga.
Dalam dua puluh tahun yang lalu, kami telah beralih daripada tapak pengehosan statik murah kepada pengehosan kongsi dengan tindanan LAMP kepada menyewa VPS kepada AWS, Azure dan perkhidmatan awan yang lain. Pada masa kini, banyak syarikat berpuas hati dengan membuat keputusan reka bentuk berdasarkan sesiapa sahaja yang mereka dapati yang tersedia atau paling murah memandangkan kemunculan perkhidmatan awan yang mudah telah memudahkan untuk membuang lebih banyak perkakasan pada pelayan dan aplikasi yang perlahan. Ini telah memberi mereka keuntungan jangka pendek yang besar dengan kos hutang teknikal jangka panjang.
70 tahun yang lalu, terdapat perlumbaan angkasa lepas yang hebat antara Kesatuan Soviet dan Amerika Syarikat. Soviet memenangi kebanyakan pencapaian awal. Mereka mempunyai satelit pertama di Sputnik, anjing pertama di angkasa lepas di Laika, kapal angkasa bulan pertama di Luna 2, lelaki dan wanita pertama di angkasa di Yuri Gagarin dan Valentina Tereshkova, dan sebagainya...
Tetapi mereka perlahan-lahan mengumpul hutang teknikal.
Walaupun Soviet pertama kali mencapai setiap pencapaian ini, proses dan matlamat kejuruteraan mereka menyebabkan mereka menumpukan pada cabaran jangka pendek dan bukannya kebolehlaksanaan jangka panjang. Mereka menang setiap kali mereka melompat, tetapi mereka semakin letih dan semakin perlahan sementara lawan mereka terus mengorak langkah konsisten menuju ke garisan penamat.
Sebaik sahaja Neil Armstrong mengambil langkah bersejarahnya di bulan di televisyen secara langsung, Amerika memimpin, dan kemudian tinggal di sana ketika program Soviet goyah. Ini tidak berbeza dengan syarikat hari ini yang telah memberi tumpuan kepada perkara besar seterusnya, hasil besar seterusnya, atau teknologi besar seterusnya sambil gagal membangunkan tabiat dan strategi yang betul untuk jangka masa panjang.
Menjadi yang pertama ke pasaran tidak bermakna anda akan menjadi pemain dominan dalam pasaran itu. Sebagai alternatif, meluangkan masa untuk melakukan perkara yang betul tidak menjamin kejayaan, tetapi sudah tentu meningkatkan peluang anda untuk pencapaian jangka panjang. Jika anda peneraju teknologi untuk syarikat anda, pilih arah dan alatan yang betul untuk beban kerja anda. Jangan biarkan populariti menggantikan prestasi dan kecekapan.
Ingin memuat turun fail 7z yang mengandungi skrip Rust, ExpressJS, Flask, Starlette dan Pure PHP?
Mengenai Pengarang |
|
![]() |
Jim telah membuat pengaturcaraan sejak dia mendapat IBM PS/2 kembali pada tahun 90-an. Sehingga hari ini, dia masih lebih suka menulis HTML dan SQL dengan tangan, dan memberi tumpuan kepada kecekapan dan ketepatan dalam kerjanya. |