cosmopolitan/tool/net/demo/binarytrees.lua

190 lines
5.9 KiB
Lua
Raw Normal View History

-- 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()