Ya, Virginia, Ada *Ada* a Santa Claus Perbezaan Antara Rangka Kerja Web pada 2023

Satu perjalanan pengaturcara yang mencabar untuk mencari kod pelayan web yang berprestasi pantas sebelum dia tunduk kepada tekanan pasaran dan hutang teknikal
2023-03-24 11:52:06
๐Ÿ‘๏ธ 785
๐Ÿ’ฌ 0

kandungan

  1. pengenalan
  2. Ujian
  3. PHP/Laravel
  4. PHP tulen
  5. Meninjau semula Laravel
  6. Django
  7. Kelalang
  8. Starlette
  9. Node.js/ExpressJS
  10. Karat/Actix
  11. Hutang Teknikal
  12. Sumber

pengenalan

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.

Ujian

Persekitaran Pengujian

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

Tugas di Tangan

Kod kami akan mempunyai tiga tugas mudah untuk setiap permintaan:

  1. Baca ID sesi pengguna semasa daripada kuki
  2. Muatkan maklumat tambahan daripada pangkalan data
  3. Kembalikan maklumat tersebut kepada pengguna

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.

Kandungan sessionvars.js

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.

Keperluan Kod

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.

Menunjukkan berapa kali anda telah melawat halaman tersebut

Data sesi setiap pengguna akan disimpan dalam pangkalan data PostgreSQL.

Jadual pengguna

Dan jadual pangkalan data ini akan dipotong sebelum setiap ujian.

Meja selepas dipotong

Mudah tetapi berkesan ialah moto Pafera... di luar garis masa paling gelap pula...

Keputusan Ujian Sebenar

PHP/Laravel

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?

PHP tulen

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?

Meninjau semula 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.

Django

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&#x27;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.

Kelalang

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&#x27; 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.

Starlette

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.

Node.js/ExpressJS

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&amp;R C atau Knuth&#x27;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&#x27; 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 &quot;Ni!&quot;.

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.

Karat/Actix

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.

Hutang Teknikal

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.

Amaran Ketua Pakar Bedah California: Ini bukan anjing angkasa sebenar.

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.

Sumber

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.