Hello clf,
The forth webserver I talked about before now runs on VFX and ciforth
(lina) as well. lina only tested in its 32-bit configuration.
From the following ApacheBench numbers you can conclude... that
SwiftForth's FILE-SIZE is quite slow (it seeks to the end of the file
instead of statting it). Otherwise I'm not sure what to say about
performance, except that lina's more competitive than I expected for
this workload. These numbers come from a Dell XPS 12 with CPUs in
'performance' mode as it wasn't running on battery.
The 'hello' columns are served by hello-website, which just responds
to any HTTP request with 'hello'. The 'index' columns are served by
simple-website, and are responding to the front-page query with the
contents of a brief index.html. There's also a game-website but it
just mixes 'hello' performance with 'simple' performance, depending on
the query.
ab -c 1 -n 10000 -r
http://127.0.0.1:4000/
Requests per second:
-c 1 -c 2 -c 10
hello index | hello index | hello index
sf 10312.51 6139.10 | 14027.51 13474.13 | 15286.13 15819.26
lina 15493.14 8971.72 | 13985.37 15104.36 | 14934.04 14921.36
vfx 9756.34 9052.46 | 13856.13 16168.67 | 13198.67 13526.11
In the process of expanding the number of Forths that can run the
webserver, I've broken (temporarily) the epoll engine. The threaded
engine's still broken. Although some of the changes would seem to
have made things worse for multithreading, I don't think it's that bad
off. It just needs attention.
epoll was working fine with VFX for a while. In the previous commit
actually.
As part of this process I removed SWOOP from everything but some
SF-only code, and replaced it with packages (for namespacing) and
chibi tables (for data structures). SWOOP was already less pleasant
for VFX, which didn't have [OBJECTS ... OBJECTS] locals syntax.
The time I put into trying to get even PortableSWOOP to run under
ciforth was pretty wasted.
For the most part removing SWOOP improved the code. Not to fault
SWOOP. That's how it worked out.
Routing, initially:
routing begin:
route :: index-rule
if: client request request-uri s" /" compare 0=
if s" /index.html" client request 'request-uri 2! then false ;
route :: google-rule
if: client request ua s" Googlebot" search nip nip ;
do: client kill ; \ close connection without logging
simple :: time-rule
if: m" /time" ;
do: client created @ .now ;
...
routing end;
But that request stuff used CLIENT as an injected local object
variable. I couldn't get that working with PortableSwoop, so this code
briefly became:
routing begin:
route :: index-rule
:noname [ over -> match-xt ! ] >r
r@ USING client request request-uri s" /" compare 0=
if s" /index.html" r@ USING client request 'request-uri 2! then false r> drop ;
route :: google-rule
:noname [ over -> match-xt ! ]
USING client request ua s" Googlebot" search nip nip ;
:noname [ over -> fulfill-xt ! ]
USING client kill ; \ close connection without logging
simple :: time-rule
:noname [ over -> match-xt ! ] drop m" /time" ;
:noname [ over -> fulfill-xt ! ] USING client created @ .now ;
...
routing end;
Not *quite* as pretty.
It now looks like this:
routing
rewrite / /index.html
:noname request. ua s" Googlebot" search nip nip ;
:noname client. kill ;
when /time
:noname [OK client. created @ .now OK] ;
...
end-routing
Before I had 'route' objects with match and fulfill methods, and
subclasses of them, and they were put into a linked list with its own
methods. Now I just have normal definitions and helpers that create
definitions, and it all gets compiled into a single definition:
: routing ( -- )
depth route-depth !
routes >order ;
PRIVATE
: end-routing ( xt xt ... xt -- )
depth route-depth @ - n>r
:NONAME
r> begin dup while 1-
r> COMPILE,
dup if 1-
r@ ['] noop = if r> drop else
POSTPONE if r> COMPILE, POSTPONE exit POSTPONE then
then
then
repeat drop
POSTPONE ; is route previous ;
...
So this:
routing
' A
' B
' C
' NOOP
' D
' E
' F
end-routing
Gets compiled into this:
:NONAME A IF B EXIT THEN C D IF E EXIT THEN F ;
Meanwhile a lot of former objects have former methods that are
actually completely unchanged (this was a lot of the point of chibi
tables' design), and former OBJ METHOD invocations now mostly look
like PACKAGE. WORD
So this:
: connected ( -- )
request clear reading on now created ! fd on written off
status off peer off buffer off
socket @ socket. fd peername if drop peer ! then ;
Just became this:
: connected ( -- )
request. clear reading on now created ! fd on written off
status off peer off buffer off
socket. fd peername if drop peer ! then ;
On porting, getting it run with VFX was very easy. I just had to
remove some advanced SWOOP stuff, mostly. VFX does seem to completely
silently fail in cases where SF has verbose and nice error messages,
OTOH debugging VFX is much smoother when it drops you right into an
editor on the line with the error. But I had some nice-with-SF
constructs that were wasted on VFX:
( at the beginning of poll/pollserver.fs )
[DEFINED] clients 0= [IF] abort
package clients
package-peeker client.
max-clients 1+ table client
end-table
: connected ( -- ) pollfd now has a live socket ;
: reap ( -- ) clean up, log death, etc. pollfd will be killed afterwards ;
: update ( -- ) you have input! (or may now write). deal with it ;
end-package
[THEN]
If you tried to load this with SF it'll fail and show you the context
of the failure -- oh, right, this code assumes a clients package and
table.
Porting the webserver to ciforth required:
1. giving up on an advanced OOP system altogether
2. reimplementing core words like GET-ORDER and S\"
3. making mistakes in #2 which were extremely confusing because I'd
made them a long time ago and didn't think of core words as 'in scope'
for errors.
4. asking C for a lot of extra syscall numbers and some data offstes
5. dealing with C *lying* about #4. I have no idea why my ->st_size
calculation is wrong, I just know (experimentally) that it should be
some other number that I've hardcoded.
6. Adding "don't do this on ciforth" conditional code:
$ grep -P '(?<!: )\[CI\]' *.fs */*.fs|wc -l
11
But it was still something I only worked on for two days, not counting
stuff like the package+chibi replacement plan.
If there's a gforth release that has a C interface that doesn't demand
a lot from the system (like a C compiler) or a syscall interface
(something like ciforth's XOS and XOS5 primitives is all that's
*really* needed), then it'll of course work easily, too.
I have some iForth code but it needs some extra build system work if
I'm not to say "Step 1. provide the following words somehow", so I've
put it aside for now.
That is all. This is all still a work in progress.
The code can be found at
https://bitbucket.org/demonview/forth-httpd/
I am using fossil for development and am just pushing with git, so
don't expect much of interest in git metadata.
-- Julian