logo

drewdevault.com

[mirror] blog and personal website of Drew DeVault git clone https://hacktivis.me/git/mirror/drewdevault.com.git

Slow.md (9977B)


  1. ---
  2. date: 2020-01-04
  3. layout: basic
  4. title: Hello world
  5. ---
  6. Let's say you ask your programming language to do the simplest possible task:
  7. print out "hello world". Generally this takes two syscalls: write and exit.
  8. The following assembly program is the ideal Linux x86_64 program for this
  9. purpose. A perfect compiler would emit this hello world program for any
  10. language.
  11. ```
  12. bits 64
  13. section .text
  14. global _start
  15. _start:
  16. mov rdx, len
  17. mov rsi, msg
  18. mov rdi, 1
  19. mov rax, 1
  20. syscall
  21. mov rdi, 0
  22. mov rax, 60
  23. syscall
  24. section .rodata
  25. msg: db "hello world", 10
  26. len: equ $-msg
  27. ```
  28. Most languages do a whole lot of other crap other than printing out "hello
  29. world", even if that's all you asked for.
  30. <table class="table table-bordered">
  31. <thead>
  32. <tr>
  33. <th>Test case</th>
  34. <th>Source</th>
  35. <th>Execution time</th>
  36. <th>Total syscalls</th>
  37. <th>Unique syscalls</th>
  38. <th>Size (KiB)</th>
  39. </tr>
  40. </thead>
  41. <tbody>
  42. <tr>
  43. <td><strong>Assembly</strong> (x86_64)</td>
  44. <td>
  45. <a href="#tests">test.S</a>
  46. </td>
  47. <td>0.00s real</td>
  48. <td>2</td>
  49. <td>2</td>
  50. <td>8.6 KiB*</td>
  51. </tr>
  52. <tr>
  53. <td><strong>Zig</strong> (small)</td>
  54. <td>
  55. <a href="#testzig">test.zig</a>
  56. </td>
  57. <td>0.00s real</td>
  58. <td>2</td>
  59. <td>2</td>
  60. <td>10.3 KiB</td>
  61. </tr>
  62. <tr>
  63. <td><strong>Zig</strong> (safe)</td>
  64. <td>
  65. <a href="#testzig">test.zig</a>
  66. </td>
  67. <td>0.00s real</td>
  68. <td>3</td>
  69. <td>3</td>
  70. <td>11.3 KiB</td>
  71. </tr>
  72. <tr>
  73. <td><strong>C</strong> (musl, static)</td>
  74. <td>
  75. <a href="#testc">test.c</a>
  76. </td>
  77. <td>0.00s real</td>
  78. <td>5</td>
  79. <td>5</td>
  80. <td>95.9 KiB</td>
  81. </tr>
  82. <tr>
  83. <td><strong>C</strong> (musl, dynamic)</td>
  84. <td>
  85. <a href="#testc">test.c</a>
  86. </td>
  87. <td>0.00s real</td>
  88. <td>15</td>
  89. <td>9</td>
  90. <td>602 KiB</td>
  91. </tr>
  92. <tr>
  93. <td><strong>C</strong> (glibc, static*)</td>
  94. <td>
  95. <a href="#testc">test.c</a>
  96. </td>
  97. <td>0.00s real</td>
  98. <td>11</td>
  99. <td>9</td>
  100. <td>2295 KiB</td>
  101. </tr>
  102. <tr>
  103. <td><strong>C</strong> (glibc, dynamic)</td>
  104. <td>
  105. <a href="#testc">test.c</a>
  106. </td>
  107. <td>0.00s real</td>
  108. <td>65</td>
  109. <td>13</td>
  110. <td>2309 KiB</td>
  111. </tr>
  112. <tr>
  113. <td><strong>Rust</strong></td>
  114. <td>
  115. <a href="#testrs">test.rs</a>
  116. </td>
  117. <td>0.00s real</td>
  118. <td>123</td>
  119. <td>21</td>
  120. <td>244 KiB</td>
  121. </tr>
  122. <tr>
  123. <td><strong>Crystal</strong> (static)</td>
  124. <td>
  125. <a href="#testcr">test.cr</a>
  126. </td>
  127. <td>0.00s real</td>
  128. <td>144</td>
  129. <td>23</td>
  130. <td>935 KiB</td>
  131. </tr>
  132. <tr>
  133. <td><strong>Go</strong> (static w/o cgo)</td>
  134. <td>
  135. <a href="#testgo">test.go</a>
  136. </td>
  137. <td>0.00s real</td>
  138. <td>152</td>
  139. <td>17</td>
  140. <td>1661 KiB</td>
  141. </tr>
  142. <tr>
  143. <td><strong>D</strong> (dmd)</td>
  144. <td>
  145. <a href="#testd">test.d</a>
  146. </td>
  147. <td>0.00s real</td>
  148. <td>152</td>
  149. <td>20</td>
  150. <td>5542 KiB</td>
  151. </tr>
  152. <tr>
  153. <td><strong>D</strong> (ldc)</td>
  154. <td>
  155. <a href="#testd">test.d</a>
  156. </td>
  157. <td>0.00s real</td>
  158. <td>181</td>
  159. <td>21</td>
  160. <td>10305 KiB</td>
  161. </tr>
  162. <tr>
  163. <td><strong>Crystal</strong> (dynamic)</td>
  164. <td>
  165. <a href="#testcr">test.cr</a>
  166. </td>
  167. <td>0.00s real</td>
  168. <td>183</td>
  169. <td>25</td>
  170. <td>2601 KiB</td>
  171. </tr>
  172. <tr>
  173. <td><strong>Go</strong> (w/cgo)</td>
  174. <td>
  175. <a href="#testgo">test.go</a>
  176. </td>
  177. <td>0.00s real</td>
  178. <td>211</td>
  179. <td>22</td>
  180. <td>3937 KiB</td>
  181. </tr>
  182. <tr>
  183. <td><strong>Perl</strong></td>
  184. <td>
  185. <a href="#testpl">test.pl</a>
  186. </td>
  187. <td>0.00s real</td>
  188. <td>255</td>
  189. <td>25</td>
  190. <td>5640 KiB</td>
  191. </tr>
  192. <tr>
  193. <td><strong>Java</strong></td>
  194. <td>
  195. <a href="#testjava">Test.java</a>
  196. </td>
  197. <td>0.07s real</td>
  198. <td>226</td>
  199. <td>26</td>
  200. <td>15743 KiB</td>
  201. </tr>
  202. <tr>
  203. <td><strong>Node.js</strong></td>
  204. <td>
  205. <a href="#testjs">test.js</a>
  206. </td>
  207. <td>0.04s real</td>
  208. <td>673</td>
  209. <td>40</td>
  210. <td>36000 KiB</td>
  211. </tr>
  212. <tr>
  213. <td><strong>Python 3</strong> (PyPy)</td>
  214. <td>
  215. <a href="#testpy">test.py</a>
  216. </td>
  217. <td>0.68s real</td>
  218. <td>884</td>
  219. <td>32</td>
  220. <td>9909 KiB</td>
  221. </tr>
  222. <tr>
  223. <td><strong>Julia</strong></td>
  224. <td>
  225. <a href="#testjl">test.jl</a>
  226. </td>
  227. <td>0.12s real</td>
  228. <td>913</td>
  229. <td>41</td>
  230. <td>344563 KiB</td>
  231. </tr>
  232. <tr>
  233. <td><strong>Python 3</strong> (CPython)</td>
  234. <td>
  235. <a href="#testpy">test.py</a>
  236. </td>
  237. <td>0.02s real</td>
  238. <td>1200</td>
  239. <td>33</td>
  240. <td>15184 KiB</td>
  241. </tr>
  242. <tr>
  243. <td><strong>Ruby</strong></td>
  244. <td>
  245. <a href="#testrb">test.rb</a>
  246. </td>
  247. <td>0.04s real</td>
  248. <td>1401</td>
  249. <td>38</td>
  250. <td>1283 KiB</td>
  251. </tr>
  252. </tbody>
  253. </table>
  254. <div style="text-align: right">
  255. <small>* See notes for this test case</small>
  256. </div>
  257. This table is sorted so that the number of syscalls goes up, because I reckon
  258. more syscalls is a decent metric for how much shit is happening that you didn't
  259. ask for (i.e. `write("hello world\n"); exit(0)`). Languages with a JIT fare much
  260. worse on this than compiled languages, but I have deliberately chosen not to
  261. account for this.
  262. These numbers are real. This is more complexity that someone has to debug, more
  263. time your users are sitting there waiting for your program, less disk space
  264. available for files which actually matter to the user.
  265. ### Environment
  266. Tests were conducted on January 3rd, 2020.
  267. - gcc 9.2.0
  268. - glibc 2.30
  269. - musl libc 1.1.24
  270. - Linux 5.4.7 (Arch Linux)
  271. - Linux 4.19.87 (vanilla, Alpine Linux) is used for musl libc tests
  272. - Go 1.13.5
  273. - Rustc 1.40.0
  274. - Zig 0.5.0
  275. - OpenJDK 11.0.5 JRE
  276. - Crystal 0.31.1
  277. - NodeJS 13.5.0
  278. - Julia 1.3.1
  279. - Python 3.8.1
  280. - PyPy 7.3.0
  281. - Ruby 2.6.4p114 (2019-10-01 rev 67812)
  282. - dmd 1:2.089.0
  283. - ldc 2:1.18.0
  284. - Perl 5.30.1
  285. For each language, I tried to write the program which would give the most
  286. generous scores without raising eyebrows at a code review. The size of all
  287. files which must be present at runtime (interpreters, stdlib, libraries, loader,
  288. etc) are included. Binaries were stripped where appropriate.
  289. This was not an objective test, this is just an approximation that I hope will
  290. encourage readers to be more aware of the consequences of their abstractions,
  291. and their exponential growth as more layers are added.
  292. ### test.S
  293. ```
  294. bits 64
  295. section .text
  296. global _start
  297. _start:
  298. mov rdx, len
  299. mov rsi, msg
  300. mov rdi, 1
  301. mov rax, 1
  302. syscall
  303. mov rdi, 0
  304. mov rax, 60
  305. syscall
  306. section .rodata
  307. msg: db "hello world", 10
  308. len: equ $-msg
  309. ```
  310. ```
  311. nasm -f elf64 test.S
  312. gcc -o test -static -nostartfiles -nostdlib -nodefaultlibs
  313. strip test: 8.6 KiB
  314. ```
  315. **Notes**
  316. - This program only works on x86_64 Linux.
  317. - The size depends on how you measure it:<br />
  318. *Instructions + data alone*: 52 bytes<br />
  319. *Stripped ELF*: 8.6 KiB<br />
  320. *Manually minified ELF*: [142 bytes](http://timelessname.com/elfbin/)
  321. ### test.zig
  322. ```
  323. const std = @import("std");
  324. pub fn main() !void {
  325. const stdout = try std.io.getStdOut();
  326. try stdout.write("hello world\n");
  327. }
  328. ```
  329. ```
  330. # small
  331. zig build-exe test.zig --release-small --strip
  332. # safe
  333. zig build-exe test.zig --release-safe --strip
  334. ```
  335. **Notes**
  336. - Written with the assistance of Andrew Kelly (maintainer of Zig)
  337. ### test.c
  338. ```
  339. int puts(const char *s);
  340. int main(int argc, char *argv[]) {
  341. puts("hello world");
  342. return 0;
  343. }
  344. ```
  345. ```
  346. # dynamic
  347. gcc -O2 -o test test.c
  348. strip test
  349. # static
  350. gcc -O2 -o test -static test.c
  351. strip test
  352. ```
  353. **Notes**
  354. - glibc programs can never truly be statically linked. The size reflects this.
  355. ### test.rs
  356. ```
  357. fn main() {
  358. println!("hello world");
  359. }
  360. ```
  361. ```
  362. rustc -C opt-levels=s test.rs
  363. ```
  364. **Notes**
  365. - The final binary is dynamically linked with glibc, which is included in the
  366. size.
  367. ### test.go
  368. ```
  369. package main
  370. import "os"
  371. func main() {
  372. os.Stdout.Write([]byte("hello world\n"))
  373. }
  374. ```
  375. ```
  376. # dynamic
  377. go build -o test test.go
  378. # static w/o cgo
  379. GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o test -ldflags '-extldflags "-f no-PIC -static"' -buildmode pie -tags 'osusergo netgo static_build' test.go
  380. ```
  381. Aside: it is getting way too goddamn difficult to build static Go binaries.
  382. **Notes**
  383. - The statically linked test was run on Alpine Linux with musl libc. It doesn't
  384. link to libc in theory, but hey.
  385. ### Test.java
  386. ```
  387. public class Test {
  388. public static void main(String[] args) {
  389. System.out.println("hello world");
  390. }
  391. }
  392. ```
  393. ```
  394. javac Test.java
  395. java Test
  396. ```
  397. ### test.cr
  398. ```
  399. puts "hello world\n"
  400. ```
  401. ```
  402. # Dynamic
  403. crystal build -o test test.cr
  404. # Static
  405. crystal build --static -o test test.cr
  406. ```
  407. **Notes**
  408. - The Crystal tests were run on Alpine Linux with musl libc.
  409. ### test.js
  410. ```
  411. console.log("hello world");
  412. ```
  413. ```
  414. node test.js
  415. ```
  416. ### test.jl
  417. ```
  418. println("hello world")
  419. ```
  420. ```
  421. julia test.jl
  422. ```
  423. **Notes**
  424. - Julia numbers were provided by a third party
  425. ### test.py
  426. ```
  427. print("hello world")
  428. ```
  429. ```
  430. # cpython
  431. python3 test.py
  432. # pypy
  433. pypy3 test.py
  434. ```
  435. ### test.pl
  436. ```
  437. print "hello world\n"
  438. ```
  439. ```
  440. perl test.pl
  441. ```
  442. **Notes**
  443. - Passing /dev/urandom into perl is equally likely to print "hello world"
  444. ### test.d
  445. ```
  446. import std.stdio;
  447. void main()
  448. {
  449. writeln("hello world");
  450. }
  451. ```
  452. ```
  453. # dmd
  454. dmd -O test.d
  455. # ldc
  456. ldc -O test.d
  457. ```
  458. ### test.rb
  459. ```
  460. puts "hello world\n"
  461. ```
  462. ```
  463. ruby test.rb
  464. ```