the informal ramblings of a formal language researcher

Saturday, October 29, 2011

Bonjour de Paris

I have arrived in Paris!  It is great.  Stephanie has been posting pictures on her blog.

I had to leave the majority of my library at home, but I did take a special set of textbooks with me: my old editions of TAOCP, all three volumes.  I am hoping to work through volumes 2 and 3, and post the results of my experimental implementations here, the same way I posted the code for algorithm 7.2.1.1-W earlier.  I have many more languages available at my fingertips now: in addition to the old-standbys, Java, C, and Scheme, I now also have ActionScript, Javascript, and C++.  Imight also dabble in a bit of assembly code in x86_64 or ARM from time to time.

At the same time, I am experimenting with alternative tools for authoring blog content.  I'm trying to be a good little Adobe employee and try to use our own tools (Dreamweaver in this case) to interface with Blogger. My end goal is to post active Javascript and Flash content on the web, in addtion to the transcripts I post.  But that might require I shift over to my personal web server, rather than work through Blogger.  We shall see.

Thursday, August 27, 2009

from 90sec to 9sec.

I have been reading Knuth's pre-fascicles from Chapter 7 of TAOCP.

Last night when I transcribed one of the algorithms (7.2.1.1-W) to Scheme code, I got caught up in Knuth's claim that the iterative process described should take "at most a few seconds", yet my first version was taking 90 seconds to run in Larceny. So I started the human iterative process of revising the code, inspecting the compiler output, and doing more runs to figure out where the time was going.

At the end of it, I got the running time down to 9 seconds (from 90; that's a 10x speed-up!) You can shave off a few more seconds by switching to unsafe mode via compiler switches, but it seems like the really important thing was understanding which operations were going to turn into native machine instructions, which ones would turn into millicode invocations in the C runtime, and which ones would turn into Scheme procedure invocations.

(I think 9 qualifies as "a few seconds", especially for a dynamic language like Scheme.)

So here, for your viewing pleasure, is both the first and last versions of the code, along with the log in the comments of the changes I made and what effect they seemed to have.


;; ----------------------------------------------------------------------
;; What is largest set of five letter words connected via single letter
;; swaps? To find out, we use specialized representation of sgb-words,
;; where each word is mapped to an index in a bit-table with 2^25 entries
;; (I would represent each bit by a byte for ease of coding, but Larceny
;; cannot construct objects >= 16 MB, so my hand is forced.)

(define bytevector-bit-ref
(lambda (bv k)
(let ((q (quotient k 8))
(r (remainder k 8)))
(fxarithmetic-shift-right
(fxand (bytevector-u8-ref bv q)
(fxarithmetic-shift-left 1 r)) r))))

(define bytevector-bit-ref?
(lambda (bv k)
(not (zero? (bytevector-bit-ref bv k)))))

(define bytevector-bit-set!
(lambda (bv k bit)
(let ((q (quotient k 8))
(r (remainder k 8)))
(bytevector-u8-set!
bv q
(fxior (fxand (bytevector-u8-ref bv q)
(fxxor #b11111111 (fxarithmetic-shift-left 1 r)))
(fxarithmetic-shift-left bit r))))))

(define (word->index w)
(let ((char->num (lambda (c) (- (char->integer c) (char->integer #\a))))
(offsets (map (lambda (e) (expt 2 e)) '(20 15 10 5 0))))
(apply + (map * (map char->num (string->list w))
offsets))))

(define (index->word i)
(let ((num->char (lambda (n) (integer->char (+ n (char->integer #\a)))))
(offsets (map (lambda (e) (expt 2 e)) '(20 15 10 5 0))))
(list->string (map num->char (map (lambda (o) (remainder (quotient i o) (expt 2 5)))
offsets)))))

(define (read-sgb-words file)
(call-with-input-file file
(lambda (p)
(let loop ((words '()))
(let ((x (read-line p)))
(cond
((eof-object? x)
(reverse words))
(else
(loop (cons x words)))))))))

(define (make-sgb-words-table words)
(let ((table (make-bytevector (quotient (+ (expt 2 25) 7) 8))))
(for-each (lambda (x) (bytevector-bit-set! table (word->index x) 1))
words)
table))

(define words (read-sgb-words "sgb-words.txt"))
(define t (make-sgb-words-table words))

;; This procedure takes 96sec on Chimaera.
;; Knuth says this process should finish in "at most a few seconds"; see below.
(define (find-max-intermix t words)
(let ((A-best '())
(l-best 0)
(wiz (map word->index words))
(two^25 (expt 2 25))
(two^20 (expt 2 20))
(two^15 (expt 2 15))
(two^10 (expt 2 10))
(two^5 (expt 2 5)))
(let loop1 ((w1 wiz))
(cond
((not (null? w1))
(let loop2 ((w2 (cdr w1)))
(cond
((null? w2)
(loop1 (cdr w1)))
(else
(let* ((w (car w1))
(w* (car w2))
(z (fxxor w w*))
(m (+ two^20 two^15 two^10 two^5 1))
(m* (* two^5 m)))
(cond
((not (zero? (fxand (fxxor (fx- z m) z m) m*)))
(loop2 (cdr w2)))
(else
(let ((m (vector (fxand z (fx- two^5 1)) ; m_0
(fxand z (fx- two^10 two^5)) ; m_1
(fxand z (fx- two^15 two^10)) ; m_2
(fxand z (fx- two^20 two^15)) ; m_3
(fxand z (fx- two^25 two^20)) ; m_4
)))
(let ((l 1) (A (list w)))
(let* ((sw (lambda (j)
(set! w (fxxor w (vector-ref m j)))
(cond ((bytevector-bit-ref? t w)
(set! l (+ l 1))
(set! A (cons w A))))))
(swap-0 (lambda () (sw 0)))
(swap-1 (lambda () (swap-0) (sw 1) (swap-0)))
(swap-2 (lambda () (swap-1) (sw 2) (swap-1)))
(swap-3 (lambda () (swap-2) (sw 3) (swap-2)))
(swap-4 (lambda () (swap-3) (sw 4) (swap-3))))
(swap-4)
(cond ((> l l-best)
(display A) (newline)
(set! l-best l)
(set! A-best A))))))
(loop2 (cdr w2)))))))))))))


;; Knuth says this process should finish in "at most a few seconds" but its
;; taking Chimaera 96.4sec; lets see if allowing more inlining helps matters...
;; - Using internal define for bytevector-bit-ref and bytevector-bit-ref?
;; shaved off 6 seconds to 90.1sec
;; - Using macros to define { sw, swap-0, swap-1, swap-2, swap-3, swap-4 }
;; brings time down to 77.7sec
;; - Using macros to define bytevector-bit-ref and bytevector-bit-ref?
;; did not improve the time at all... (but I did not do it properly the first time!)
;; - Lifting the definitions of m and m* did not improve the time.
;; - Replacing the encoding of m_i via a 5-elem vector with explicit
;; variables { m_0 .. m_4 } may have shaved a fraction of a second off
;; the time...
;; - Fixing the macro for bytevector-bit-ref and bytevector-bit-ref? did not help.
;; - Replacing fxarithmetic-shift-left and fxarithmetic-shift-right with fxlsh and fxrsh
;; was huge; brought time down to 40.5sec
;; - Changing encoding of 2^k to thunks rather than let-bound constants did not help anything.
;; - Changing encoding of 2^k to macros rather than thunks did not help anything [[ but I may have been timing the wrong code here ]]
;; - Changing encoding of m and m' to macros rather than let-bound constants did not help anything.
;;
;; [[ note on last three items: it looks strongly like Twobit is
;; reintroducing the let-bindings for temporaries rather than
;; inline their values in the code, which is interesting.]]
;; [[ (are fxlsh and friends not interpreted at compile time?) ]]

;; - Manually constant folding fxlsh and friends got down to 39.8sec (maybe not statistically sig delta).
;; - Adding check for bytevector? of t at outset got down to 39.1sec (again maybe not stat sig on own).
;; - Switching to multi-valued fxdiv-and-mod was a terrible idea (x2.5 slowdown)
;; - And even using fxdiv fxmod separately was a terrible idea (x2 slowdown)

;; [[ We apparently do not have any compiler optimizations for fxquotient and similar; hmm. ]]

;; - Changing the code to use fxrhsl and fxand instead of quotient and remainder
;; brings running time down to 9sec (from between 37 and 39 seconds; another huge leap!).

(define (find-max-intermix.v16 t words)

(let*-syntax ((bytevector-bit-ref
(syntax-rules ()
((_ bv k)
(let ((q (fxrshl k 3))
(r (fxand k #b111)))
(fxrsha
(fxand (bytevector-u8-ref bv q)
(fxlsh 1 r)) r)))))

(bytevector-bit-ref?
(syntax-rules ()
((_ bv k)
(not (zero? (bytevector-bit-ref bv k))))))

(two^25 (syntax-rules () ((_) 33554432)))
(two^20 (syntax-rules () ((_) 1048576)))
(two^15 (syntax-rules () ((_) 32768)))
(two^10 (syntax-rules () ((_) 1024)))
(two^5 (syntax-rules () ((_) 32)))
(m (syntax-rules () ((_) 1082401 ))) ;; (+ (two^20) (two^15) (two^10) (two^5) 1)
(m* (syntax-rules () ((_) 34636832 ))) ;; (* (two^5) (m)))))
)

(let* ((A-best '())
(l-best 0)
(wiz (map word->index words)))
(cond
((not (bytevector? t)) (error))
(else
(let loop1 ((w1 wiz))
(cond
((not (null? w1))
(let loop2 ((w2 (cdr w1)))
(cond
((null? w2)
(loop1 (cdr w1)))
(else
(let* ((w (car w1))
(w* (car w2))
(z (fxxor w w*)))
(cond
((not (fxzero? (fxand (fxxor (fx- z (m)) z (m)) (m*))))
(loop2 (cdr w2)))
(else
(let ((m_0 (fxand z (fx- (two^5) 1)))
(m_1 (fxand z (fx- (two^10) (two^5))))
(m_2 (fxand z (fx- (two^15) (two^10))))
(m_3 (fxand z (fx- (two^20) (two^15))))
(m_4 (fxand z (fx- (two^25) (two^20)))))
(let ((l 1) (A (list w)))
(let*-syntax ((upd (syntax-rules ()
((upd) (cond ((bytevector-bit-ref? t w)
(set! l (+ l 1))
(set! A (cons w A)))))))
(sw (syntax-rules ()
((sw 0) (begin (set! w (fxxor w m_0)) (upd)))
((sw 1) (begin (set! w (fxxor w m_1)) (upd)))
((sw 2) (begin (set! w (fxxor w m_2)) (upd)))
((sw 3) (begin (set! w (fxxor w m_3)) (upd)))
((sw 4) (begin (set! w (fxxor w m_4)) (upd)))))
(swap-0 (syntax-rules ()
((_) (sw 0))))
(swap-1 (syntax-rules ()
((_) (begin (swap-0) (sw 1) (swap-0)))))
(swap-2 (syntax-rules ()
((_) (begin (swap-1) (sw 2) (swap-1)))))
(swap-3 (syntax-rules ()
((_) (begin (swap-2) (sw 3) (swap-2)))))
(swap-4 (syntax-rules ()
((_) (begin (swap-3) (sw 4) (swap-3))))))
(begin
(swap-4)
(cond ((> l l-best)
(display A) (newline)
(set! l-best l)
(set! A-best A)))))))
(loop2 (cdr w2))))))))))))))))


UPDATE: I realized it might be worthwhile to identify how much speed-up would have resulted if I had just applied the most important steps to the original version at the outset.

The answer is that just:

  1. Turning the bit-referencing operations into internal defines within the procedure, and

  2. Using the fxlsh, fxrshl, and fxand operations for the definitions of the bit-referencing operations


brings the running time down to 18 seconds. So somewhere around 70 seconds of the time was spent on inefficiencies due to using general purpose arithmetic operations when fixnum operations (that the compiler specializes) would probably suffice for most people; no need to use macros to force an inlined swap(4) operation (which is what Knuth recommends in his text).

Here is the 18 second version. Note that its code bytevector after being compiled by Twobit is 4.0 kilobytes, while the more heavily optimized find-max-intermix.v16 is 17.8 kilobytes; that is a 4x space regression for a 2x speed improvement.

(define (find-max-intermix t words)

(define bytevector-bit-ref
(lambda (bv k)
(let ((q (fxrshl k 3))
(r (fxand k #b111)))
(fxrshl
(fxand (bytevector-u8-ref bv q)
(fxlsh 1 r)) r))))

(define bytevector-bit-ref?
(lambda (bv k)
(not (zero? (bytevector-bit-ref bv k)))))

(let ((A-best '())
(l-best 0)
(wiz (map word->index words))
(two^25 (expt 2 25))
(two^20 (expt 2 20))
(two^15 (expt 2 15))
(two^10 (expt 2 10))
(two^5 (expt 2 5)))
(let loop1 ((w1 wiz))
(cond
((not (null? w1))
(let loop2 ((w2 (cdr w1)))
(cond
((null? w2)
(loop1 (cdr w1)))
(else
(let* ((w (car w1))
(w* (car w2))
(z (fxxor w w*))
(m (+ two^20 two^15 two^10 two^5 1))
(m* (* two^5 m)))
(cond
((not (zero? (fxand (fxxor (fx- z m) z m) m*)))
(loop2 (cdr w2)))
(else
(let ((m (vector (fxand z (fx- two^5 1)) ; m_0
(fxand z (fx- two^10 two^5)) ; m_1
(fxand z (fx- two^15 two^10)) ; m_2
(fxand z (fx- two^20 two^15)) ; m_3
(fxand z (fx- two^25 two^20)) ; m_4
)))
(let ((l 1) (A (list w)))
(let* ((sw (lambda (j)
(set! w (fxxor w (vector-ref m j)))
(cond ((bytevector-bit-ref? t w)
(set! l (+ l 1))
(set! A (cons w A))))))
(swap-0 (lambda () (sw 0)))
(swap-1 (lambda () (swap-0) (sw 1) (swap-0)))
(swap-2 (lambda () (swap-1) (sw 2) (swap-1)))
(swap-3 (lambda () (swap-2) (sw 3) (swap-2)))
(swap-4 (lambda () (swap-3) (sw 4) (swap-3))))
(swap-4)
(cond ((> l l-best)
(display A) (newline)
(set! l-best l)
(set! A-best A))))))
(loop2 (cdr w2)))))))))))))

mismatch twixt formals and invocation forms

As a follow up to my yet-another-generator macro post:

yesterday at lunch I was trying to describe my problems generalizing my generator macro to handle arbitrary lambda formals lists. Ryan was the one who summed the problem up best; I think he said "there is a asymmetry between the format of the formals list and the format of invocation"

The problem in essence is that there are two distinct cases that are easy.
If you want to match a proper list of formals and generate a corresponding invocation, you do something like:

(define-syntax adder
(syntax-rules ()
((_ (args ...) k ...)
(lambda (args ...) (+ k ... args ...)))))


And, if you want to do variable arity, you can do it by matching an identifier representing the whole list of arguments

(define-syntax adder
(syntax-rules ()
((_ argl k ...)
(lambda argl (apply + k ... argl)))))


But notice the difference between the bottom and the top. If some users of the adder macro want to be able to specify that they are generating a procedure of arity 2, with appropriate error detection, they can use the first but not the second. And of course, the first does not do variable arity procedures.

To deal with this problem in the context of my generator macro, I introduced a helper macro that builds up an application expression based on the form of the formals list.


(define-syntax build-apply
(syntax-rules ()
((build-apply (rator rands ...) (args ...)) ;; usual case
(rator rands ... args ...))
((build-apply (rator rands ...) (x . y)) ;; improper list, inductive case
(build-apply (rator rands ... x) y))
((build-apply (rator rands ...) rest-args) ;; improper list, base case
(apply rator rands ... rest-args))))


The order of all three clauses above is very significant.

With that in place, here is the new form of my generator macro(s):

;; (generator ARGS BODY ...) => procedure
;; in whose body yield is bound to coroutine with caller
(define-syntax generator
(transformer
(lambda (exp ren cmp)
`(,(ren 'generator-via) yield ,(cadr exp) ,@(cddr exp)))))

(define-syntax generator-via
(syntax-rules ()
((generator-via yield-id args body ...)
(let ((get-next #f))
(lambda args
(call-with-current-continuation
(lambda (exit)
(cond
(get-next (build-apply (get-next exit) args))
(else (let ((yield-id
(lambda results
(call-with-current-continuation
(lambda (next)
(set! get-next (lambda (new-exit . args)
(set! exit new-exit)
(build-apply (next) args)))
(apply exit results))))))
(call-with-values (lambda () body ...)
;; below could also use build-apply with
;; args, but then the "done-value"(s)
;; returned from a generator would be forced
;; to have value-count = length args
(lambda argl
(set! get-next (lambda (new-exit . args)
(apply new-exit argl)))
(apply exit argl)))))))))))))

Tuesday, August 25, 2009

yet another yield Scheme macro

I have seen yield done plenty of times before, but I managed to reinvent it again tonight.

A lot of the time when I've played with call/cc, I have approached it trying to recreate a form in a statement-oriented language, and so I would end up not caring about the argument I would pass to the invocation of the captured continuation (because all I cared about was reestablishing the control flow of the prior context; the prior context itself would just discard any value I passed to it). I think the usual interpretation of yield is an instance of this in statement oriented languages.

However, when I was playing with some code tonight, I saw that dummy value and said, "ugly! why is that there?" And I decided to see what would happen if I got rid of it.

This macro is the result. (Obviously you want a bit of sugar around it to non-hygenically bind yield, although there are cases where it is nice to bind a different name like visit instead of yield, depending on the domain.)


(define-syntax generator-via
(syntax-rules ()
;; puzzle 4 U: what role(s) does arg-list serve? (all occurrences matter)
((generator-via yield-id arg-list body ...)
(let ((yield-id #f) (get-next #f))
(lambda arg-list
(call-with-current-continuation
(lambda (exit)
(cond (get-next (get-next exit . arg-list))
(else (set! yield-id (lambda results
(call-with-current-continuation
(lambda (next)
(set! get-next
(lambda (new-exit . arg-list)
(set! exit new-exit)
(next . arg-list)))
(apply exit results)))))
(call-with-values (lambda () body ...)
;; puzzle 4 U: why below eta-expansion required?
(lambda args (apply exit args))))))))))))


Its got some fun behaviors. Consider:


> (define grows
(generator-via yield (x)
(let loop ((i 0))
(loop (+ i x (yield i))))))

> (grows 1)
0

> (grows 0)
1

> (grows 0)
2

> (grows 0)
3

> (grows 1)
5

> (grows 0)
6



(yes I just realized I could/should have let-bound the yield-id itself. The other set! invocations are believed to be necessary, barring tricks mixing letrec and call/cc.)

Wednesday, July 29, 2009

(no) interpreters on iphone

I'm collecting blog posts and web pages discussing clause 3.3.2 of the iPhone SDK EULA and semi-related restrictions on iPhone apps.

mobilephonedevelopment article

rhomobile's interpretation of no interpreters rule

story of how a Basic interpreter got downgraded to a calculator (and how to bring the interpreter back if you're willing to jailbreak).

PhoneGap Discussion of arbitrariness of rejection

MacNN coverage of the PhoneGap issue.

Commodore 64 emulator rejection article

FrotZ got through the filter; I asked the developer about that in this discussion

Tuesday, July 21, 2009

git-svn argh!

CCIS decommissioned a number of its Solaris machines recently.

I was using one of them as the point of access for the Larceny SVN repository, via the svn+ssh protocol.

More recently I have been playing with git-svn, using that as my way to interact with the repository. I made a local git-svn clone of the Larceny SVN repository, and then I would clone the clone and make branches in the cloned clones as part of my experimentation with new ideas. Fun fun fun.

Except that CCIS decommissioned a number of its Solaris machines recently, and I was using one of them as the point of access, which mean that the repository url looked something like svn+ssh://zulu.ccs.neu.edu/path-to-repos/, and git-svn (somewhat reasonably) assumes that these URLS would remain valid forever, and puts a copy of that URL into every log message of the clone.

Now, its easy to update those log messages; that's what git-filter-branch is for. (See also this related blog entry.)

But what's not so easy is dealing with the sudden split in the tree structure of the development on my cloned clones; all of my local deltas seem to point to the old changesets with the old bad URL. And "git rebase" does not seem to be magically able to handle this case.

Thus: git-svn argh!

If the SVN folks though that a "svn switch --relocate" command was important, maybe the git-svn developer(s) should also consider their design decisions...

Monday, January 12, 2009

Zecreasing Zerived Zvariables

This is a great little story. And probably something I could explain to my Mom.

http://www.freedom-to-tinker.com/blog/felten/debugging-zune-blackout

(plus I've had this obsession with date-oriented code lately...)

(p.s. can one even pronounce "Zvariables"?)

Friday, January 09, 2009

5 or 6 forced restarts later...

I wasted some time this evening trying to use gprof to profile Larceny.

Eventually I discovered that the -pg option simply does not work with gcc on Intel Macs. (Why on earth doesn't the man page for gprof say something about this?)

Apple instead recommends that one use Shark or Saturn, both provided when you install the CHUD Tools (Computer Hardware Understanding Developer Tools)

So I did that. I downloaded a copy of the CHUD Tools (and as far as I can remember, I did it by searching on ADC for them, and got a copy of chud.dmg). I was a little surprised by the file times (they said something like 2007) but I pressed on, eager to start seeing some profiler output, especially since all of my searches to read about Shark sounded very positive about it.

And then when I attempted to compile with -finstrument-functions, so I could use Saturn in the same way that I might have used gprof, I got a link error:

Undefined symbols:
"___cyg_profile_func_exit", referenced from:
_consolemsg in larceny.o
_consolemsg in larceny.o
...
"___cyg_profile_func_enter", referenced from:
_panic_abort in larceny.o
_panic_exit in larceny.o
...


I abandoned further attempts to use Saturn pretty quickly.

So then I tried Shark. And that was, if anything, worse. Because every time I tried to stop a profiling session, it would restart my machine!

Eventually I discovered (though not easily) this discussion post.

Hmm. CHUD 4.4.4 panics. I look at my version. Yep, its 4.4.4.

So I did a more careful search for a more recent version of CHUD (4.6.1, specifically). We'll see how that works out for me in a bit.

(I'm mostly posting this so that other people who run into a similar problem know that they need to get a newer version of CHUD. Maybe later I'll try to retrace my steps that led me to thinking that CHUD 4.4.4 was the version I needed to use...)

  • I do not know who linked to it, but some page (that I believe was on the Apple site) sent me to the follow ftp link, which has the version of CHUD that is incompatible with newer versions of OS X: ftp://ftp.apple.com/developer/Tool_Chest/Testing_-_Debugging/Performance_tools/CHUD_4.4.4.dmg

Monday, December 15, 2008

CFER : "suitable for blogs"

ICFP 2009 has a "Call For Experience Reports", and specifically mentions that its suitable for posting on blogs.

So here goes. :)

ICFP 2009: CFER

Wednesday, July 02, 2008

data structures, network outages, and php

The most "productive" portion of my day was spent prototyping the interface to summary set matrix that I will be putting into the regional collector RSN. I have the header file worked out and started working on the representation itself. I got part way through writing a constructor function before deciding that I wanted to think a bit more about how I want to handle allocation and deallocation of the entries in the sparse matrix.

(Some of that time was spent reviewing the existing source for the Larceny runtime, especially the different attribute bits one can put on pages of memory in Larceny, and the history of when they were introduced and/or shifted around by Lars...)

I also had a back-and-forth with the Systems staff since artichoke and poblano became inaccessible from the outside world at some point yesterday evening.

I finished off the day trying to figure out why my GC benchmarking script is still failing on the dynregion/ branch. I have more of a clue about it now, but am pretty unhappy about how much of a kludge this supposedly simpler script is becoming. (Maybe I should give up doing these things in shell script and just do them as Larceny scripts.)

Monday, June 30, 2008

hi after hiatus

This weekend I mentioned that I had a blog to my family, and then showed the url to Stephanie.

I proceeded to become engrossed in reading over my old posts; I had forgotten some of the interesting software experiments I had been posting online.

Of course, I have not made a post in almost two years. I have since switched to keeping an internal work diary, so that I would not worry about giving away the farm (or embarrassing outcomes of my day to day research), and also posting any interesting links that I wanted to see later to my del.icio.us account

But I realized after looking at the old entries that I liked broadcasting my progress.

So I am going to try to maintain this blog again, and see if I can figure out how to distill my daily work diary into something small (and publicly distributable) each day (or perhaps every other day or so... who knows).

So, today:

  • Rasputin's disk was full; I had to wrestle with that for a little while. It seems like each disk fills up on such a regular basis that I really should consider fixing the autobuild scripts to clean up after themselves or to compress their generated directories or both...

  • I finally checked in a fix to Larceny's simplistic stopcopy collector on the IAssassin backend so that when allocation fills up a chunk in a semispace, the runtime first attempts to switch the allocation pointer to a free chunk within the semispace rather than attempt a whole heap collection. This is a crucial fix, because the stopcopy collector's performance had asymptotically terrible performance before, which affected both our benchmark results and some of our users who wanted to load a lot of code before performing a heap dump.

  • I spent some time trying to double-check the runtime source code's make dependencies, because I thought I saw evidence that not everything was being rebuilt properly in response to changes I made for fixing the stopcopy collector. Unfortunately I was unable to pin down any particular missing dependency.

  • I tried generalizing the nightly GC benchmarking script so that it could also benchmark my dynregion development branch. But then my attempts to just double-check the behavior of the GC benchmarking script itself failed, and my investigations there led me to file Ticket #547 in Larceny's trac database.



I also spent some time browsing the web/google groups/etc. I added some links to my aforementioned del.icio.us account. I don't think I'll be explicitly mentioning such activity on my part in the future.

Sunday, September 10, 2006

Oh hdiutil, what can't you do?

(orig article)

The punchline:

hdiutil convert image.dmg -format UDTO -o image.iso

Although this makes a file with the extension .cdr, the article claims that it is an iso file and can be renamed as such.

This allows me to use the OS X Disk Utility to create .iso images that can then be mounted from Parallels desktop or VMware.

UPDATE:
The above article seems to be a complete lie, but at least it pointed me at hdiutil... the above approach makes iso images that Mac OS X can mount, but do not work within Parallels.

However, the following command seems like it does the job:

hdiutil makehybrid -o image image.dmg

Tuesday, August 08, 2006

stupid LEA tricks

LEA is the "load effective address" instruction on x86 processors.

Despite the "L" in the name, this is solely an arithmetic instruction. Which means there are some nasty little games you can play with it. Like this one I got courtesy of Frank Kotler from:

http://groups.google.com/group/alt.lang.asm/msg/27d6ed6183448057?hl=en&


global _start

section .data
number_string db '123', 10

section .text
_start:
nop

push number_string
call atoi
add esp, byte 4

mov ebx, eax
mov eax, 1
int 80h

atoi:
mov edx, [esp + 4] ; pointer to string
xor eax, eax ; clear "result"
.top:
movzx ecx, byte [edx]
inc edx
cmp ecx, byte '0'
jb .done
cmp ecx, byte '9'
ja .done

; we have a valid character - multiply
; result-so-far by 10, subtract '0'
; from the character to convert it to
; a number, and add it to result.

lea eax, [eax + eax * 4]
lea eax, [eax * 2 + ecx - 48]

jmp short .top
.done
ret

Tuesday, July 18, 2006

free smalltalk! (books, that is)

Just discovered this page today:On a related note, here are some other free books online that I wasn't aware of until recently:

Wednesday, May 03, 2006

321 contact!

Larceny's just ran x86 code in heap for the first time!

The initial scheme code:
(define seg (assemble (compile 321)))
(define env (environment-copy (interaction-environment)))
(define cseg (sassy-postpass-segment seg))
(define linked (link-lop-segment cseg env))

Note that the "program" we're compiling here is just evaluating the literal 321. Not very exciting to interpret, but to run off the heap, marvelous!

The (insane) hack on the x86 implementation of MacScheme's invoke:
 dec dword [GLOBALS+G_TIMER]
jnz short %%L1
%%L0: mcall M_INVOKE_EX
jmp short %%L1
%%L2: storer 0, RESULT
const2regf RESULT, fixnum(%1)
int3 ;; debugging interrupt
;;; (the lack of space is significant; gdb ignores "int 3")
add TEMP,-BVEC_TAG+BVEC_HEADER_BYTES
jmp TEMP
%%L1: lea TEMP, [RESULT+(8-PROC_TAG)]
test TEMP_LOW, tag_mask
jnz short %%L0
;; Felix inserted checks if we actually have a
;; fixnum here...
mov TEMP, [TEMP-8+PROC_CODEVECTOR_NATIVE]
test TEMP_LOW, fixtag_mask
jnz short %%L2
storer 0, RESULT
const2regf RESULT, fixnum(%1)
jmp TEMP

The heart of the hack is that invoke is now dispatching based on the kind of code pointer it got. This should allow me to keep using Petit for the heart of the system, but still test the code in the heap directly.
  • For fixnums (which are actually addresses into the text segment, but don't tell the garbage collector), we do the old invocation routine.

  • For bytevector pointers (which should point at codevectors in the heap), strip the tag off the pointer, then add the offset to skip over the bytevector header (on the heap object itself). Finally, JUMP!

And, at long last, the actual result:
(gdb) r -heap sassy.heap
Starting program: /home/pnkfelix/larcenydev/trunk/larceny_src/twobit.bin -heap sassy.heap
(no debugging symbols found)...(no debugging symbols found)...(no debugging symbols found)...(no debugging symbols found)...LARCENY_ROOT not set; using current directory
Larceny v0.91 "Children's Ice Cream" (May 2 2006 10:41:54, precise:Linux:unified)


> (load "testsass.scm")

> (linked)

Program received signal SIGTRAP, Trace/breakpoint trap.
0x08342d49 in ..@6866.L2 ()
(gdb) stepi
0x08342d4c in ..@6866.L2 ()
(gdb) stepi
0x00767454 in ?? ()
(gdb) c
Continuing.
321

Did you see it??? Did you??? That 321 at the end, that was produced by code in the heap. (Well, the value was; the actual action of printing the value is still happening from code in the text segment).

Woo hoo!!!

Wednesday, February 01, 2006

on parallelizing a garbage collector

So here are the lessons I learned from attempting to parallelize a stop-and-copy garbage collector, as implemented in the Larceny runtime, using Cilk's primitives as the basis for the parallelization.

At a high level, based on Flood's paper on parallel collection on SMP machines, I decided to start with a naive recursive stop-and-copy collection algorithm, and parallelize that using Cilk's spawn and sync primitives. (The Flood paper points out the utility of a work-stealing approach to load-balancing, which the Cilk runtime gives you "for free.")

Of course, the devil is in the details. First of all, all I had to start with was an kernel collector that uses Cheney's algorithm, which is not trivial to parallelize (as far as I can tell). So I had to develop a recursive algorithm that behaved in a manner compatible with the behavior of the Cheney collector (this is not as trivial as it sounds; the devil's in the details).

Even after I developed the recursive algorithm, I discovered a new set back: the Cilk language, as currently documented, has a restriction that you can only call Cilk functions from other Cilk functions. That is, Cilk functions can call out to C functions, but not vice versa.

This immediately leads to a huge problem for Larceny, because the Scheme code is meant to be able to call into the garbage collector. So that means that the Scheme code needs to be output as Cilk code, not C code. And in fact, the entire runtime and compiler system needs to be shifted to use Cilk instead of C. ACK!

Luckily, the very newest version of Cilk has a beta interface for calling Cilk functions from C code. Unluckily, the interface is indeed beta, and there were some bugs that were showstoppers for me (in hindsight, there were workarounds for the bugs, but that's neither here nor there).

So, here's the important lessons from the project:

  1. Reverse engineering is hard. Even when you're going from a complex algorithm to a simple one, there's always details in "real systems" that get overlooked. (For me, it was particular invariants in terms of the Cheney algorithm deliberately not traversing certain structures at certain times, and it was overloading the generation bits to control this, even though it was still relevant even for non-generational collection.

  2. If you want your language extension/variant A to interoperate with language B, it needs to go in both directions. You may think it will suffice to have one layered on top of the other, but in the end, you will be wrong, and your users will hate you. You're better off designing that interoperability in from the beginning. (This is relevant for languages that seek to interoperate with the CLR or the JVM, in that your extension language should define classes that themselves can be invoked or extended by terms in C# or Java, respectively.

Tuesday, January 03, 2006

They ID'ed me 100%

Bourbon
Congratulations! You're 118 proof, with specific scores in beer (20) , wine (100), and liquor (78).
Screw all that namby-pamby chick stuff, you're going straight for the bottle and a shot glass! It'll take more than a few shots of Wild Turkey or 99 Bananas before you start seeing pink elephants. You know how to handle your alcohol, and yourself at parties.



My test tracked 4 variables How you compared to other people your age and gender:
free online datingfree online dating
You scored higher than 30% on proof
free online datingfree online dating
You scored higher than 50% on beer index
free online datingfree online dating
You scored higher than 84% on wine index
free online datingfree online dating
You scored higher than 66% on liquor index

Link: The Alcohol Knowledge Test written by hoppersplit on Ok Cupid, home of the 32-Type Dating Test

Sunday, November 13, 2005

Swapping Ctrl/CapsLock (PCs or Macs!)

Someone else cares about this, and he cares enough to catalogue how to do it on all sorts of machines!
Just go here.

(hmm. Actually, that was a total lie; the link above only treats windows and linux machines. Well, this guys says how to do it on 10.4 machines.)

Thursday, November 03, 2005

some windows stuff worth knowing.


  • This page tells you how to get free copies of the command line development tools that are including in Visual Studio. That's right, get yourself the C/C++ compiler and header files (as well as some batch scripts to set up your environment properly), all direct from Microsoft.


    • .NET Framework SDK (for the compiler and linker).

    • Platform SDK (for the header files).

    • As a side note to this, I had to learn about the call statement for DOS Batch scripts in order to learn how to make a batch file that would call each of the batch files in sequence, because each of the above SDK has a different batch file to set up the environment.


  • This page tells you how to edit the Windows Registry to change the behavior of the Caps Lock key. For a Emacs user like me, this was a crucial thing to learn, since the control key is really hard to get at on modern Windows laptops. Speaking of which . . .

  • This page tells you how to install Emacs on a Windows machine. It works well enough.

  • Cygwin. Nuff said.

Wednesday, August 31, 2005

GC'ing classes

.NET as far as I can tell, does not garbage collect unreachable code. At best, you can try to manually manage the memory associated with dynamically loaded code by loading into separate AppDomains that you unload by hand. I have not really experimented with this option.

I was discussing this problem with a friend, who asserted that Java has the same problem.

To prove him wrong, I wrote the following class. You run it on the command line, passing an numeric argument that indicates the number of distinct classes you want to load. Try it out with and without the -Xnoclassgc option in Java!

import java.lang.Integer;
import java.lang.ClassLoader;
import java.lang.Class;
import java.lang.ClassNotFoundException;

public class ClassSFS {

public static void println(String s) {
System.out.println(s);
}
public static void println() {
System.out.println();
}
public static void print(String s) {
System.out.print(s);
}
public static void main(String[] args) {
println("Hello World");
int num_classes = Integer.parseInt(args[0]);
initbytes();
for(int i = 0; i < num_classes; i++) {

Tbytes[ 48 + 5 ] = (byte) (0x61 + (i/100) % 26);
Tbytes[ 48 + 6 ] = (byte) (0x61 + (i/10) % 26);
Tbytes[ 48 + 7 ] = (byte) (0x61 + (i/1) % 26);

CLoader cl = new CLoader();
try {
Class c = cl.findClass("T");
Object x = c.newInstance();
System.out.println("ClassLoader "+i+", x:"+x);
} catch (ClassNotFoundException e) {
System.out.println("ClassLoader "+i+", ClassNotFound e:"+e);
} catch (InstantiationException e) {
System.out.println("ClassLoader "+i+", InstantiationException e:"+e);
} catch (IllegalAccessException e) {
System.out.println("ClassLoader "+i+", IllegalAccessException e:"+e);
}
}
}

static class CLoader extends ClassLoader {
CLoader() { super(); }
public Class findClass(String name) throws ClassNotFoundException {
return
super.defineClass(name,
Tbytes,
0,
Tbytes.length);
}
}


private static int[] iTbytes = {

0xca, 0xfe, 0xba, 0xbe, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x20, 0x0a, 0x00, 0x0a, 0x00, 0x16, 0x07,
0x00, 0x17, 0x0a, 0x00, 0x02, 0x00, 0x16, 0x08, 0x00, 0x18, 0x0a, 0x00, 0x02, 0x00, 0x19, 0x09,
0x00, 0x09, 0x00, 0x1a, 0x0a, 0x00, 0x02, 0x00, 0x1b, 0x08, 0x00, 0x0b, 0x07, 0x00, 0x1c, 0x07,
/* f e e */
0x00, 0x1d, 0x01, 0x00, 0x03, 0x66, 0x65, 0x65, 0x01, 0x00, 0x12, 0x4c, 0x6a, 0x61, 0x76, 0x61,
0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3b, 0x01, 0x00, 0x06,
0x3c, 0x69, 0x6e, 0x69, 0x74, 0x3e, 0x01, 0x00, 0x03, 0x28, 0x29, 0x56, 0x01, 0x00, 0x04, 0x43,
0x6f, 0x64, 0x65, 0x01, 0x00, 0x0f, 0x4c, 0x69, 0x6e, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72,
0x54, 0x61, 0x62, 0x6c, 0x65, 0x01, 0x00, 0x08, 0x74, 0x6f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67,
0x01, 0x00, 0x14, 0x28, 0x29, 0x4c, 0x6a, 0x61, 0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x2f,
0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3b, 0x01, 0x00, 0x08, 0x3c, 0x63, 0x6c, 0x69, 0x6e, 0x69,
0x74, 0x3e, 0x01, 0x00, 0x0a, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x01,
0x00, 0x06, 0x54, 0x2e, 0x6a, 0x61, 0x76, 0x61, 0x0c, 0x00, 0x0d, 0x00, 0x0e, 0x01, 0x00, 0x16,
0x6a, 0x61, 0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67,
0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x01, 0x00, 0x03, 0x54, 0x3c, 0x3e, 0x0c, 0x00, 0x1e, 0x00,
0x1f, 0x0c, 0x00, 0x0b, 0x00, 0x0c, 0x0c, 0x00, 0x11, 0x00, 0x12, 0x01, 0x00, 0x01, 0x54, 0x01,
0x00, 0x10, 0x6a, 0x61, 0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x4f, 0x62, 0x6a, 0x65,
0x63, 0x74, 0x01, 0x00, 0x06, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x01, 0x00, 0x2c, 0x28, 0x4c,
0x6a, 0x61, 0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67,
0x3b, 0x29, 0x4c, 0x6a, 0x61, 0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x53, 0x74, 0x72,
0x69, 0x6e, 0x67, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x3b, 0x00, 0x21, 0x00, 0x09, 0x00, 0x0a,
0x00, 0x00, 0x00, 0x01, 0x00, 0x0a, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01,
0x00, 0x0d, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x01, 0x00, 0x01,
0x00, 0x00, 0x00, 0x05, 0x2a, 0xb7, 0x00, 0x01, 0xb1, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00,
0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x11, 0x00, 0x12, 0x00,
0x01, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x16, 0xbb,
0x00, 0x02, 0x59, 0xb7, 0x00, 0x03, 0x12, 0x04, 0xb6, 0x00, 0x05, 0xb2, 0x00, 0x06, 0xb6, 0x00,
0x05, 0xb6, 0x00, 0x07, 0xb0, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x06, 0x00,
0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, 0x13, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x0f, 0x00,
0x00, 0x00, 0x1e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x12, 0x08, 0xb3, 0x00, 0x06,
0xb1, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00,
0x02, 0x00, 0x01, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x15

};

private static byte[] Tbytes;

public static void initbytes()
{
Tbytes = new byte[ iTbytes.length ];
for(int i = 0; i < iTbytes.length; i++) {
Tbytes[i] = (byte)(iTbytes[i]);
}

print(""+nybble2char((Tbytes[0]>>4) & 0xF));
print(""+nybble2char((Tbytes[0]>>0) & 0xF));
print(""+nybble2char((Tbytes[1]>>4) & 0xF));
print(""+nybble2char((Tbytes[1]>>0) & 0xF));
print(""+nybble2char((Tbytes[2]>>4) & 0xF));
print(""+nybble2char((Tbytes[2]>>0) & 0xF));
print(""+nybble2char((Tbytes[3]>>4) & 0xF));
print(""+nybble2char((Tbytes[3]>>0) & 0xF));
println();
}

private static char nybble2char(int b) {
switch (b) {
case 0xf: return 'f';
case 0xe: return 'e';
case 0xd: return 'd';
case 0xc: return 'c';
case 0xb: return 'b';
case 0xa: return 'a';
default: return (char) (b+'a');
}
}

public static int Tcounter = 0;
}


The iTbytes array was generated by compiling public class T { private static String fee = "fee"; public String toString() { return "T<>"+fee; }}, then loading the resulting class file into emacs, switching to hexl-mode, and doing some keyboard-macrology to convert it to something javac would accept.

Followers