概要
- 「Hello World な HTML を返す程度の Web アプリケーション」または「それぞれの公式ドキュメントに載っているチュートリアルの初期状態に近いもの」を作成してメモリ使用量を比較する
- Web アプリケーションサーバは現時点でよく使われていると思われるものを使用する
- テンプレートエンジンはフレームワークのデフォルト設定またはフレームワークが推奨しているものを使用する
- macOS Catalina 上でメモリ使用量を計測
- メモリ使用量の計測には ps コマンドの RSS 値を使う (複数のプロセスを使うものはRSS値を単純に足したものをメモリ使用量とした。メモリ共有部分については考慮していないため、もう少しメモリ使用量が少ないかもしれない)
調査結果
メモリ使用量が少ない順に並べる。
- 7MB: Go 1.13 + Gin 1.5
- 10MB: Go 1.13 + Revel 0.21
- 24MB: Ruby 2.7 + Sinatra 2.0 + Puma 4.3
- 40MB: Python 3.8 + Flask 1.1 + Gunicorn 20.0
- 42MB: Node.js 12.14 + Express 4.16
- 51MB: Python 3.8 + Django 3.0 + Gunicorn 20.0
- 80MB: Ruby 2.7 + Ruby on Rails 6.0 + Puma 4.3
- 167MB: Java 11.0 + Spring Boot 2.2 + Tomcat Embed Core 9.0
計測環境の構築と計測
Go 1.13 + Gin 1.5
- Gin Web Framework
- 構成: Go 1.13.5 + Gin 1.5.0
main.go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.LoadHTMLGlob("templates/*")
router.GET("/hello", func(c *gin.Context) {
c.HTML(http.StatusOK, "hello.tmpl", gin.H{})
})
router.Run(":8080")
}
templates/hello.tmpl
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello, World!</title>
</head>
<body>
Hello, World!
</body>
</html>
$ go build main.go
$ GIN_MODE=release ./main
$ curl http://localhost:8080/hello
$ ps au
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
hoge 30713 0.0 0.1 4405168 7220 s000 S+ 12:04AM 0:00.03 ./main
Go 1.13 + Revel 0.21
- Welcome to Revel, the Web Framework for Go!
- 構成: Go 1.13.5 + Revel 0.21.0
Creating a new Revel application | Revel - A Web Application Framework for Go!
$ revel new myapp
Revel tool | Revel - A Web Application Framework for Go!
$ revel package -a myapp -m prod
Deployment | Revel - A Web Application Framework for Go!
$ tar zxvf myapp.tar.gz
run.sh
#!/bin/sh
SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
"$SCRIPTPATH/myapp" -importPath myapp -srcPath "$SCRIPTPATH/src" -runMode prod
$ ./run.sh
Revel engine is listening on.. localhost:9000
$ curl http://localhost:9000/
$ ps au
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
hoge 32777 0.0 0.1 4405468 8928 s000 S+ 12:32AM 0:00.04 /Users/hoge/go/myapp
hoge 32771 0.0 0.0 4288312 1048 s000 S+ 12:32AM 0:00.01 /bin/sh ./run.sh
Ruby 2.7 + Sinatra 2.0 + Puma 4.3
- Sinatra
- 構成: Ruby 2.7.0 + Sinatra 2.0.8.1 + Erb + Puma 4.3.1
myapp.rb
require 'sinatra'
get '/hello' do
erb :hello
end
hello.erb
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello, World!</title>
</head>
<body>
Hello, World!
</body>
</html>
$ APP_ENV=production RACK_ENV=production ruby myapp.rb
== Sinatra (v2.0.8.1) has taken the stage on 4567 for production with backup from Puma
Puma starting in single mode...
* Version 4.3.1 (ruby 2.7.0-p0), codename: Mysterious Traveller
* Min threads: 0, max threads: 16
* Environment: production
* Listening on tcp://0.0.0.0:4567
$ curl http://localhost:4567/hello
$ ps au
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
hoge 99121 0.0 0.3 4378332 24540 s002 S+ 10:18PM 0:00.43 puma 4.3.1 (tcp://0.0.0.0:4567)
Python 3.8 + Flask 1.1 + Gunicorn 20.0
- Flask | The Pallets Projects
- 構成: Python 3.8.0 + Flask 1.1.1 + Jinja2 2.10.3 + Gunicorn 20.0.4
myapp.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/hello")
def hello():
return render_template("hello.html")
templates/hello.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello, World!</title>
</head>
<body>
Hello, World!
</body>
</html>
$ gunicorn myapp:app
[2020-01-04 23:55:18 +0900] [29978] [INFO] Starting gunicorn 20.0.4
[2020-01-04 23:55:18 +0900] [29978] [INFO] Listening at: http://127.0.0.1:8000 (29978)
[2020-01-04 23:55:18 +0900] [29978] [INFO] Using worker: sync
[2020-01-04 23:55:18 +0900] [29995] [INFO] Booting worker with pid: 29995
$ curl http://localhost:8000/hello
$ ps au
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
hoge 29995 0.0 0.3 4273780 22632 s000 S+ 11:55PM 0:00.37 /Users/hoge/.pyenv
hoge 29978 0.0 0.2 4265996 18532 s000 S+ 11:55PM 0:00.46 /Users/hoge/.pyenv
Node.js 12.14 + Express 4.16
- Express - Node.js web application framework
- 構成: Node.js 12.14.0 + Express 4.16.4 + Pug 2.0.4
$ express --view=pug myapp
$ cd myapp/
$ npm install
$ npm audit fix
$ node ./bin/www
$ curl http://localhost:3000/
$ ps au
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
hoge 19537 0.0 0.5 4565284 43228 s000 S+ 9:59PM 0:00.83 node ./bin/www
Python 3.8 + Django 3.0 + Gunicorn 20.0
- The Web framework for perfectionists with deadlines | Django
- 構成: Python 3.8.0 + Django 3.0.2 + Gunicorn 20.0.4
はじめての Django アプリ作成、その 1 | Django ドキュメント | Django
$ django-admin startproject mysite
$ python manage.py startapp polls
mysite/settings.py の設定値の一部を修正。
DEBUG = False
ALLOWED_HOSTS = ['localhost', '127.0.0.1', '[::1]']
# Application definition
INSTALLED_APPS = [
'polls',
はじめての Django アプリ作成、その 3 | Django ドキュメント | Django
polls/templates/polls/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello, World!</title>
</head>
<body>
Hello, World!
</body>
</html>
polls/views.py
from django.shortcuts import render
def index(request):
return render(request, 'polls/index.html')
$ gunicorn mysite.wsgi:application
[2020-01-04 23:44:32 +0900] [29172] [INFO] Starting gunicorn 20.0.4
[2020-01-04 23:44:32 +0900] [29172] [INFO] Listening at: http://127.0.0.1:8000 (29172)
[2020-01-04 23:44:32 +0900] [29172] [INFO] Using worker: sync
[2020-01-04 23:44:32 +0900] [29191] [INFO] Booting worker with pid: 29191
$ curl http://localhost:8000/polls/
$ ps au
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
hoge 29191 0.0 0.4 4283728 34016 s000 S+ 11:44PM 0:00.55 /Users/hoge/.pyenv
hoge 29172 0.0 0.2 4265996 18552 s000 S+ 11:44PM 0:00.43 /Users/hoge/.pyenv
Ruby 2.7 + Ruby on Rails 6.0+ Puma 4.3
- Ruby on Rails | A web-application framework that includes everything needed to create database-backed web applications according to the Model-View-Controller (MVC) pattern.
- 構成: Ruby 2.7.0 + Ruby on Rails 6.0.2.1 + Erb + Puma 4.3.1
Getting Started with Rails — Ruby on Rails Guides
$ rails new blog
$ cd blog
$ rails generate controller Welcome index
app/views/welcome/index.html.erb
Hello, World
config/routes.rb
Rails.application.routes.draw do
get 'welcome/index'
root 'welcome#index'
end
$ RAILS_ENV=production rails assets:precompile
$ rails server -e production
=> Booting Puma
=> Rails 6.0.2.1 application starting in production
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 4.3.1 (ruby 2.7.0-p0), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: production
* Listening on tcp://0.0.0.0:3000
$ curl http://localhost:3000/
$ ps au
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
hoge 66416 0.0 1.0 4445976 81964 s000 S+ 9:42AM 0:02.97 puma 4.3.1 (tcp://0.0.0.0:3000) [blog]
Java 11.0 + Spring Boot 2.2 + Tomcat Embed Core 9.0
- Spring Boot
- 構成: OpenJDK 11.0.2 + Spring Boot 2.2.2 + Spring Web + Thymeleaf 3.0.11 + Tomcat Embed Core 9.0.29
build.gradle
plugins {
id 'org.springframework.boot' version '2.2.2.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
}
DemoApplication.java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@SpringBootApplication
@Controller
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@GetMapping("/hello")
public String hello() {
return "hello";
}
}
hello.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hello, World!</title>
</head>
<body>
Hello, World!
</body>
</html>
$ gradle bootJar
Java は起動直後にメモリを大きく確保してしまうので、最小ヒープサイズに32MBを指定する。
$ java -Xms32m -jar build/libs/demo-0.0.1-SNAPSHOT.jar
$ curl http://localhost:8080/hello
$ ps au
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
hoge 20294 0.0 2.0 8094612 171024 s000 S+ 10:09PM 0:17.35 java -Xms32m -jar build/libs/demo-0.0.1-SNAPSHOT.jar