mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-02-07 15:03:34 +00:00
190 lines
5.9 KiB
Lua
190 lines
5.9 KiB
Lua
|
-- binary trees: benchmark game
|
||
|
-- with resource limit tutorial
|
||
|
-- by justine tunney
|
||
|
|
||
|
local outofcpu = false
|
||
|
local oldsigxcpufunc = nil
|
||
|
local oldsigxcpuflags = nil
|
||
|
local oldsigxcpumask = nil
|
||
|
|
||
|
-- This is our signal handler.
|
||
|
function OnSigxcpu(sig)
|
||
|
-- Please note, it's dangerous to do complicated things from inside
|
||
|
-- asynchronous signal handlers. Even Log() isn't async signal safe
|
||
|
-- The best possible practice is to have them simply set a variable
|
||
|
outofcpu = true
|
||
|
end
|
||
|
|
||
|
-- These two functions are what's being benchmarked. It's a popular
|
||
|
-- torture test for interpreted languages since it generates a lot of
|
||
|
-- garbage. Lua does well on this test, going faster than many other
|
||
|
-- languages, e.g. Racket, Python.
|
||
|
|
||
|
local function MakeTree(depth)
|
||
|
if outofcpu then
|
||
|
error({reason='MakeTree() caught SIGXCPU'})
|
||
|
end
|
||
|
if depth > 0 then
|
||
|
depth = depth - 1
|
||
|
left, right = MakeTree(depth), MakeTree(depth)
|
||
|
return { left, right }
|
||
|
else
|
||
|
return { }
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function CheckTree(tree)
|
||
|
if outofcpu then
|
||
|
error({reason='CheckTree() caught SIGXCPU'})
|
||
|
end
|
||
|
if tree[1] then
|
||
|
return 1 + CheckTree(tree[1]) + CheckTree(tree[2])
|
||
|
else
|
||
|
return 1
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Now for our redbean web server code...
|
||
|
|
||
|
local function WriteForm(depth, suggestion)
|
||
|
Write([[<!doctype html>
|
||
|
|
||
|
<title>redbean binary trees</title>
|
||
|
|
||
|
<style>
|
||
|
body { padding: 1em; }
|
||
|
h1 a { color: inherit; text-decoration: none; }
|
||
|
h1 img { border: none; vertical-align: middle; }
|
||
|
input { margin: 1em; padding: .5em; }
|
||
|
p { word-break: break-word; max-width: 650px; }
|
||
|
dt { font-weight: bold; }
|
||
|
dd { margin-top: 1em; margin-bottom: 1em; }
|
||
|
.hdr { text-indent: -1em; padding-left: 1em; }
|
||
|
</style>
|
||
|
|
||
|
<h1>
|
||
|
<a href="/"><img src="/redbean.png"></a>
|
||
|
<a href="fetch.lua">binary trees benchmark demo</a>
|
||
|
</h1>
|
||
|
|
||
|
<p>
|
||
|
This demo is from the computer language benchmark game. It's a
|
||
|
torture test for the Lua garbage collector. ProTip: Try loading
|
||
|
this page while the terminal memory monitor feature is active, so
|
||
|
you can see memory shuffle around in a dual screen display. We
|
||
|
use setrlimit to set a 512mb memory limit. So if you specify a
|
||
|
number higher than 23 (a good meaty benchmark) then the Lua GC
|
||
|
should panic a bit trying to recover (due to malloc failure) and
|
||
|
ultimately the worker process should die. However the server and
|
||
|
your system will survive.
|
||
|
</p>
|
||
|
|
||
|
<form action="binarytrees.lua" method="post">
|
||
|
<input type="text" id="depth" name="depth" size="70"
|
||
|
value="%s" placeholder="%d" onfocus="this.select()"
|
||
|
autofocus>
|
||
|
<input type="submit" value="run">
|
||
|
</form>
|
||
|
|
||
|
]] % {EscapeHtml(depth), suggestion})
|
||
|
end
|
||
|
|
||
|
local function BinaryTreesDemo()
|
||
|
if GetMethod() == 'GET' or GetMethod() == 'HEAD' then
|
||
|
WriteForm("18", 18)
|
||
|
elseif GetMethod() == 'POST' and not HasParam('depth') then
|
||
|
ServeError(400)
|
||
|
elseif GetMethod() == 'POST' then
|
||
|
|
||
|
-- write out the page so user can submit again
|
||
|
WriteForm(GetParam('depth'), 18)
|
||
|
Write('<dl>\r\n')
|
||
|
Write('<dt>Output\r\n')
|
||
|
Write('<dd>\r\n')
|
||
|
|
||
|
-- impose quotas to protect the server
|
||
|
unix.setrlimit(unix.RLIMIT_AS, 1024 * 1024 * 1024)
|
||
|
unix.setrlimit(unix.RLIMIT_CPU, 2, 4)
|
||
|
|
||
|
-- when RLIMIT_AS (virtual address space) runs out of memory, then
|
||
|
-- mmap() and malloc() start to fail, and lua reacts by trying to
|
||
|
-- run the garbage collector over and over again. so we also place
|
||
|
-- a limit on the number of "cpu time" seconds too. the behavior
|
||
|
-- when we hit the soft limit, is the kernel gives us a friendly
|
||
|
-- warning by sending the signal SIGXCPU which we must catch here
|
||
|
-- once we catch it, we've got 2 seconds of grace time to clean up
|
||
|
-- before we hit the "hard limit" and the kernel sends SIGKILL (9)
|
||
|
oldsigxcpufunc,
|
||
|
oldsigxcpuflags,
|
||
|
oldsigxcpumask =
|
||
|
unix.sigaction(unix.SIGXCPU, OnSigxcpu)
|
||
|
|
||
|
-- get the user-supplied parameter
|
||
|
depth = tonumber(GetParam('depth'))
|
||
|
|
||
|
-- run the benchmark, recording how long it takes
|
||
|
secs1, nanos1 = unix.clock_gettime()
|
||
|
res = CheckTree(MakeTree(depth))
|
||
|
secs2, nanos2 = unix.clock_gettime()
|
||
|
|
||
|
-- turn 128-bit timestamps into 64-bit nanosecond interval
|
||
|
if secs2 == secs1 then
|
||
|
nanos = nanos2 - nanos1
|
||
|
else
|
||
|
nanos = (secs2 - secs1) * 1000000000 + (1000000000 - nanos1) + nanos2
|
||
|
end
|
||
|
|
||
|
-- write out result of benchmark
|
||
|
Write('0%o\r\n' % {res})
|
||
|
Write('<dt>Time Elapsed\r\n')
|
||
|
Write('<dd>\r\n')
|
||
|
Write('%g seconds, or<br>\r\n' % {nanos / 1e9})
|
||
|
Write('%g milliseconds, or<br>\r\n' % {nanos / 1e6})
|
||
|
Write('%g microseconds, or<br>\r\n' % {nanos / 1e6})
|
||
|
Write('%d nanoseconds\r\n' % {nanos})
|
||
|
Write('<dt>unix.clock_gettime() #1\r\n')
|
||
|
Write('<dd>%d, %d\r\n' % {secs1, nanos1})
|
||
|
Write('<dt>unix.clock_gettime() #2\r\n')
|
||
|
Write('<dd>%d, %d\r\n' % {secs2, nanos2})
|
||
|
Write('</dl>\r\n')
|
||
|
|
||
|
else
|
||
|
ServeError(405)
|
||
|
SetHeader('Allow', 'GET, HEAD, POST')
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function main()
|
||
|
-- catch exceptions
|
||
|
ok, err = pcall(BinaryTreesDemo)
|
||
|
|
||
|
-- we don't need to restore the old handler
|
||
|
-- but it's generally a good practice to clean up
|
||
|
if oldsigxcpufunc then
|
||
|
unix.sigaction(unix.SIGXCPU,
|
||
|
oldsigxcpufunc,
|
||
|
oldsigxcpuflags,
|
||
|
oldsigxcpumask)
|
||
|
end
|
||
|
|
||
|
-- handle exceptions
|
||
|
if not ok then
|
||
|
|
||
|
-- whenever anything, at all, goes wrong, with anything
|
||
|
-- always with few exceptions close the connection asap
|
||
|
SetHeader('Connection', 'close')
|
||
|
|
||
|
if err.reason then
|
||
|
-- show our error message withoun internal code leaking out
|
||
|
Write(err.reason)
|
||
|
Write('\r\n')
|
||
|
else
|
||
|
-- just rethrow exception
|
||
|
error('unexpected failure: ' .. tostring(err))
|
||
|
end
|
||
|
|
||
|
end
|
||
|
end
|
||
|
|
||
|
main()
|