exaloop / codon

A high-performance, zero-overhead, extensible Python compiler using LLVM
https://docs.exaloop.io/codon
Other
15.01k stars 517 forks source link

Possibility for C-like "static int written = 0" inside a function that retains its value when incremented? #453

Closed marioroy closed 1 year ago

marioroy commented 1 year ago

In C, I have a fast printint function and wish to do the same in Codon.

static const int FLUSH_LIMIT = 65536 - 24;

static inline void printint(uint64_t value, int flush_only)
{
    // Print integer to static buffer. Empty buffer automatically when full.
    // Before exiting, call printint(0,1) to empty and flush standard output.
    //
    // OMP_NUM_THREADS=4 ./primes 1e10 -p >/dev/null  #  4.6GB
    //   14.070s before, unbuffered
    //   10.012s buffered
    //
    static char buf[65536];
    static int written = 0;

    if (written && (written > FLUSH_LIMIT || flush_only)) {
        fwrite(buf, written, 1, stdout);
        written = 0;
    }
    if (flush_only) {
        fflush(stdout);
        return;
    }
    // end of buffer logic

    char c;
    int s = written, e = written;
    uint64_t temp;

    // base10 to string conversion
    do {
        temp = value / 10;
        buf[s] = (char) ('0' + value - 10 * temp);
        s++;
    } while ((value = temp));

    // increment the number of characters including linefeed
    written += s - e + 1;  buf[s] = '\n';

    // reverse the string in place
    while (--s > e) {
        c = buf[s];
        buf[s] = buf[e];
        buf[e] = c;
        e++;
    }
}

The following is the Codon version, but was hoping to not resort to global variables buf and written.

FLUSH_LIMIT: Static[int] = 65536 - 24
BUF = __array__[byte](65536)  # similar to "unsigned char buf[65536]" in C
WRITTEN = 0

@inline
def printint(value: u64, flush_only: int) -> None:

    from C import fwrite(Ptr[byte], int, int, cobj) -> int

    # Print integer to static buffer. Empty buffer automatically when full.
    # Before exiting, call printint(0,1) to empty and flush standard output.
    #
    # OMP_NUM_THREADS=4 ./primes 1e10 -p >/dev/null  #  4.6GB
    #   14.070s before, unbuffered
    #   10.012s buffered
    #
    global BUF, FLUSH_LIMIT, WRITTEN

    if WRITTEN and (WRITTEN > FLUSH_LIMIT or flush_only):
        fwrite(BUF.ptr, WRITTEN, 1, _C.seq_stdout())
        WRITTEN = 0

    if flush_only:
        _C.fflush(_C.seq_stdout())
        return

    # end of buffer logic

    s, e = WRITTEN, WRITTEN

    # base10 to string conversion
    while True:
        temp = value // u64(10)
        BUF[s] = byte(int(u64(ord('0')) + value - u64(10) * temp))
        s += 1
        value = temp
        if not value:
            break

    # increment the number of characters including linefeed
    WRITTEN += s - e + 1; BUF[s] = byte('\n')

    # reverse the string in place
    s -= 1
    while s > e:
        c = BUF[s]
        BUF[s] = BUF[e]
        BUF[e] = c
        e += 1
        s -= 1

Is it possible to declare a var similarly in Codon, that can be updated and retain its value?

static inline void printint(uint64_t value, int flush_only)
{
    static char buf[65536];
    static int written = 0;

    ...
}
marioroy commented 1 year ago

After testing, I settled with the following to not impact performance.

FLUSH_LIMIT: Static[int] = 65536 - 24

class My:
    # In C, one can declare a static variable inside a function that
    # retains its value when incremented. E.g. static int written = 0;
    # In Python, one can create a class, accessed via an object var.
    # But that reduces the performance by %5. I tried @classmethod,
    # though not yet supported in Codon. In the end, I created this
    # class to contain the few global variables. E.g. My.written += N.

    buf = Array[byte](65536)
    written = 0