Can executable size be optimized? - scheme

I have created executable of following code in Racket (choosing Racket and not GRacket):
#lang racket
(print "Hello World!")
It creates a tgz of 3.6 mb with an executable of 6.2 mb. This seems very large for this simplest program. Executable created by Chicken Scheme with same code (print "Hello World!") is of size 16984 bytes (16.6 kb) only.
I think I am missing something (possibly some optimization setting) while creating executable in Racket. How can this executable be made smaller?

The documentation for raco exe recommends using as small a base language as possible. In this case, replace #lang racket with #lang racket/base. On my machine (Linux, Racket 6.8) that drops the executable size from 6.6M to 988K.
I was able to further reduce the size of the executable by running the demodularizer first. I saved the program as hello.rkt and ran
$ raco demod hello.rkt
$ raco exe -o hello hello_rkt_merged.zo
That produces an executable of 277K.

Related

How do I get this Chicken Scheme code to compile?

Apparently my previous question was too broad. So here's the question again, simplified, and with example source code.
I'm trying to compile a Chicken Scheme project containing multiple files:
test-a.scm:
#!/usr/bin/csi -script
(declare (unit test-a))
(declare (uses test-b))
(load "test-b.scm")
(use test-b)
(test-syntax)
test-b.scm:
(declare (unit test-b))
(module test-b *
(import scheme chicken)
(define-syntax test-syntax
(syntax-rules ()
((_)
(print "In test-syntax")))))
According to the official manual, I should do it like this:
csc -c test-b.scm
csc -c test-a.scm
csc test-a.o test-b.o -o test
What I actually get is this:
Syntax error (import): cannot import from undefined module
Things to note:
I'm calling a macro.
I have a (declare (uses clause, yet csc can't find my sources.
csc test-a.scm test-b.o -o test doesn't work either.
If I remove load, the program will not work in csi.
If I remove use, the program will not work in csi.
I need the program to work in csi.
How, without breaking compatibility with csi, can I make this compile?
There are four(!) problems here:
test-a.scm contains a unit declaration. This is incorrect; there's always one file that needs to be compiled to have a main() C function. That's the file without a unit declaration. If you study the manual page you linked more closely, it says "In this case foo.scm is the main module, because it doesn't have a unit declaration".
Since you decided to use modules, you'll need to compile test-b.scm as follows: csc -c -j test-b test-b.scm. The -j switch will cause the compiler to emit a module library test-b.import.scm, which is what the compiler is looking for when compiling test-a.scm. When an import library is missing, it will complain that the module is undefined. In the interpreter it's no issue because you load the file before importing the module that it defines.
You're using load, even in the compiled version of the program. This means that it will read and evaluate the test-b.scm file (and complain if it's missing) in every situation.
You're using use, which will require the library at runtime. This is meant for loading and importing modules defined by dynamically linked libraries.
So, to solve this, you could do it like this:
test-a.scm
#!/usr/bin/csi -script
;; Declare that this uses test-b, so that its toplevel is initialised
(declare (uses test-b))
;; No (declare (unit test-a)) because this file should generate main().
;; Because we tell the compiler what to link together and we want to
;; avoid passing all the .scm files on the csi command line, we can load
;; the test-b.scm file here, but only when interpreting:
(cond-expand
((not compiling) (load "test-b.scm"))
(else))
;; Only import the module; we take care of loading the code above,
;; or in the linking step when compiling. If we had (use test-b),
;; the library would be searched for at runtime.
;; Alternatively, (use test-b) here, but add (register-feature! 'test-b)
;; to test-b.scm, which prevents the runtime from attempting to load test-b.
(import test-b)
(test-syntax)
test-b.scm (unchanged)
(declare (unit test-b))
(module test-b *
(import scheme chicken)
(define-syntax test-syntax
(syntax-rules ()
((_)
(print "In test-syntax")))))
And, to compile it:
csc -c -j test-b test-b.scm
csc -c test-a.scm
csc test-a.o test-b.o -o test
I realise this is quite a lot of stuff to know, and tricky too and some things like the use plus register-feature! simply don't make much sense. We're attempting to make this less fiddly in CHICKEN 5, and we're also going to add a FAQ to the wiki, because this really isn't obvious and a bit of a FAQ.
The manual page you linked hasn't been changed in a long time: it completely ignores the existence of modules, for example. That's why you couldn't get it to compile, the -j switch was missing because the example files in the manual page don't define modules.
Edit:
This can be cleaned up a bit because declare is only honored by the compiler anyway. So we can move that into the cond-expand as well:
test-a.scm
#!/usr/bin/csi -script
(cond-expand
(compiling (declare (uses test-b)))
(else (load "test-b.scm")))
(import test-b)
(test-syntax)

Cannot work out how to run .scm (using guile or scm) files

I have created a abc.scm file and tried to compile it to a binary (or whatever guile scheme compiles to) using "guild compile", "scm" and "guile" commands in terminal in Ubuntu.
For "guild compile abc.scm", I get the output "wrote `/home/tarunmaganti/.cache/guile/ccache/2.0-LE-8-2.0/home/tarunmaganti/abc.scm.go'"
I find the file and run like this - "./abc.scm.go" which says permission denied.
When I give the necessary permissions using "chmod 755" or "chmod 777", I get an error like - "bash: ./abc.scm.go: cannot execute binary file: Exec format error".
The "scm whatever-file-name.scm" just opens up the scm interpreter.
The "guile whatever-file-name.scm does nothing.
The link of official GNU/Guile Scheme isn't very helpful.
Please, help me. I would like to create a guile scheme script file. Compile it and run it as C/C++ program. Is it possible?
If compilation isn't possible, I would like to know, how to at least run the script file of GNU/guile scheme or MIT-Scheme.
{Step-by-step is highly appreciated, I'm still a beginner in using Ubuntu and also in Scheme.}
Thanks in advance.
You can use the shebang notation to create a Guile script (the -s is optional in newer versions of Guile):
#!/usr/bin/guile -s
!#
(display "Hello, world!\n")
Notice the !# on the second line. Guile treats the #! as the start of a block comment (similar to what #| is in standard Scheme), which has to be terminated using !# (similar to |# in standard Scheme).
If you want your script to pass any command-line options to Guile itself, then read about the meta switch. Here's an example of such:
#!/usr/bin/guile \
-e main -s
!#
(define (main args)
(if (null? (cdr args))
(format #t "Hello, world!~%")
(for-each (lambda (name)
(format #t "Hello, ~a!~%" name))
(cdr args))))
Apparently guile foo assumes that foo is a Scheme source file. If foo is a precompiled binary .go file it tries to treat it as a text file anyway and compile it a second time, which fails. I could not find any equivalent command line syntax for executing a pre-compiled .go file.
However, you can get almost the same effect as follows:
Write a source file foo.scm that exports a main procedure:
(define-module (foo)
#:export (main))
(define (main)
(display "Hello world!")
(newline))
Pre-compile it with guild compile -o foo.go foo.scm
Write a shell script that runs guile -C "$PWD" -c "(use-modules (foo)) (main)". The -C dir flag (capital C) tells it to load pre-compiled files from the directory dir. The -c expr flag (lowercase c) tells it to evaluate the Scheme code expr which in this case just does use-module to load the pre-compiled module and then calls our main procedure.
Guile's compiler is like Java's compiler: it produces bytecode that is then run in Guile's VM. I don't know of any way to go straight from Guile Scheme code to native code. Guile is really meant to be an extension language that allows you to add Scheme scripting to your C/C++ program.
I don't know much about MIT Scheme, but from what I can tell it also does not compile to a standalone executable. Feel free to correct me on that.
There's a way around all this, though. As long as you don't mind a dependency on libguile, you can write a wrapper in C/C++ and hand libguile a string containing your Scheme code. There's a basic example given in the Guile manual for Dia here. Create your script in a header file and wrap it in a null-terminated C string, then (after a bit of boilerplate and whatnot) evaluate it with scm_eval_string().
If you want to write Scheme and output native binaries, I've heard good things about Chicken.

Chicken scheme compiler (csc) errors

I have a fresh install of mingw64 and chicken scheme but whenever I try to compile a file with csc from the command line I get the following:
error: shell command terminated with non-zero exit status 1: "gcc" "foo.o" -o "foo" -wl,--enable-auto-import -Lc:\chicken-iup\lib -L"c:\chicken-iup/lib/" -lchicken -lm -lws2_32"
foo.scm:
(define (fac n)
(if (zero? n)
1
(* n (fac (- n 1)))))
(write (fac 10))
(newline)
Could someone help in figuring out why it cant compile the file? If I have a file browser open to the C:\chicken-iup\ folder I can see it makes a .c file and then makes an .o file. The error happens I assume during the linking of the .o file but I'm not certain.
Looks like you're using chicken-iup. Looking at its web page, it looks like it's designed to work with mingw, and not mingw-w64. The "incompatible" libraries are probably because mingw-w64 is designed to link in 64-bit libraries, but the chicken-iup libraries were 32-bit.
If you want to use mingw-w64, you probably have to compile CHICKEN yourself.

Reason for huge size of compiled executable of Go

I complied a hello world Go program which generated native executable on my linux machine. But I was surprised to see the size of the simple Hello world Go program, it was 1.9MB !
Why is it that the executable of such a simple program in Go is so huge?
This exact question appears in the official FAQ: Why is my trivial program such a large binary?
Quoting the answer:
The linkers in the gc tool chain (5l, 6l, and 8l) do static linking. All Go binaries therefore include the Go run-time, along with the run-time type information necessary to support dynamic type checks, reflection, and even panic-time stack traces.
A simple C "hello, world" program compiled and linked statically using gcc on Linux is around 750 kB, including an implementation of printf. An equivalent Go program using fmt.Printf is around 1.9 MB, but that includes more powerful run-time support and type information.
So the native executable of your Hello World is 1.9 MB because it contains a runtime which provides garbage collection, reflection and many other features (which your program might not really use, but it's there). And the implementation of the fmt package which you used to print the "Hello World" text (plus its dependencies).
Now try the following: add another fmt.Println("Hello World! Again") line to your program and compile it again. The result will not be 2x 1.9MB, but still just 1.9 MB! Yes, because all the used libraries (fmt and its dependencies) and the runtime are already added to the executable (and so just a few more bytes will be added to print the 2nd text which you just added).
Consider the following program:
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
If I build this on my Linux AMD64 machine (Go 1.9), like this:
$ go build
$ ls -la helloworld
-rwxr-xr-x 1 janf group 2029206 Sep 11 16:58 helloworld
I get a a binary that is about 2 Mb in size.
The reason for this (which has been explained in other answers) is that we are using the "fmt" package which is quite large, but the binary has also not been stripped and this means that the symbol table is still there. If we instead instruct the compiler to strip the binary, it will become much smaller:
$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 1323616 Sep 11 17:01 helloworld
However, if we rewrite the program to use the builtin function print, instead of fmt.Println, like this:
package main
func main() {
print("Hello World!\n")
}
And then compile it:
$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 714176 Sep 11 17:06 helloworld
We end up with an even smaller binary. This is as small as we can get it without resorting to tricks like UPX-packing, so the overhead of the Go-runtime is roughly 700 Kb.
Note that the binary size issue is tracked by issue 6853 in the golang/go project.
For instance, commit a26c01a (for Go 1.4) cut hello world by 70kB:
because we don't write those names into the symbol table.
Considering the compiler, assembler, linker, and runtime for 1.5 will be
entirely in Go, you can expect further optimization.
Update 2016 Go 1.7: this has been optimized: see "Smaller Go 1.7 binaries".
But these day (April 2019), what takes the most place is runtime.pclntab.
See "Why are my Go executable files so large? Size visualization of Go executables using D3" from Raphael ‘kena’ Poss.
It is not too well documented however this comment from the Go source code suggests its purpose:
// A LineTable is a data structure mapping program counters to line numbers.
The purpose of this data structure is to enable the Go runtime system to produce descriptive stack traces upon a crash or upon internal requests via the runtime.GetStack API.
So it seems useful. But why is it so large?
The URL https://golang.org/s/go12symtab hidden in the aforelinked source file redirects to a document that explains what happened between Go 1.0 and 1.2. To paraphrase:
prior to 1.2, the Go linker was emitting a compressed line table, and the program would decompress it upon initialization at run-time.
in Go 1.2, a decision was made to pre-expand the line table in the executable file into its final format suitable for direct use at run-time, without an additional decompression step.
In other words, the Go team decided to make executable files larger to save up on initialization time.
Also, looking at the data structure, it appears that its overall size in compiled binaries is super-linear in the number of functions in the program, in addition to how large each function is.

Compiling .scm files with MIT-SCHEME

Well, I'm trying to compile this simple scheme code using the "better" scheme interpreter/compiler MIT-SCHEME:
code.scm:
(declare (usual-integrations))
(define (s x) (* x x))
(display (s 2))
What am I doing? I'm doing this:
DXHJ ~ mit-scheme
DXHJ ~ (cf "code.scm")
;Generating SCode for file: "s.scm" => "s.bin"... done
;Compiling file: "s.bin" => "s.com"... done
;Unspecified return value
Well, after that I can't run anything. How can I generate an .out file?
It seems mit-scheme cannot compile to native, stand-alone binaries. There are other implementations that can do this. For example, Chicken scheme, Bigloo and gambit.

Resources