fmt.Sprint() performance - a case for type specialization?

658 views
Skip to first unread message

dga

unread,
Nov 14, 2009, 8:10:36 PM11/14/09
to golang-nuts
I realize it's mildly unfair to pick on performance issues at this
early stage; that said, Sprint() and friends strike me as a
compelling performance case for supporting some sorts of type
specialization. In particular, the core of Sprint is to:

v := reflect.NewValue(a).(*.StructValue);
p := newPrinter();
p.doprint(v, false, false);

A consequence of this - using reflection combined with a lot of run-
time evaluation of the type and dispatching to various formatting
routines - is that something simple, like generating a list of labels:

for i := 0; i < 100000; i++ {
out <- Sprint(i);
}

takes about 5 microseconds per call to Sprint(). In contrast, using a
specialized version of SprintInt (stolen from the print.go code for
printing integers):

var digits string = "0123456789abcdef";

func SprintInt(val int) string {
base := 10;
var buf [16]byte;
i := len(buf) - 1;
for val >= base {
buf[i] = digits[val%base];
i--;
val /= base;
}
buf[i] = digits[val];
return string(buf[i:16])
}

runs about 5x faster. Given that, at least in the case of Sprint that
I mentioned, all of the information needed to implement this
specialization is available to the compiler, it seems a shame to not
be able to "drop in" a better, happier, integer-specialized Sprint
without requiring programmers to SprintInt.
(Lest 5 microseconds per call seem tiny: In the fairly simple code I
was writing, this specialization provided a 2x speedup of the entire
application.)

It's possible I missed something obvious about how to generate string
versions of integers, of course, but Sprint() seemed the "obvious"
way. I'd be curious if there's a "Go" way to accomplish this already.

Russ Cox

unread,
Nov 14, 2009, 8:42:54 PM11/14/09
to dga, golang-nuts
> A consequence of this - using reflection combined with a lot of run-
> time evaluation of the type and dispatching to various formatting
> routines - is that something simple, like generating a list of labels:
>
> for i := 0; i < 100000; i++ {
>  out <- Sprint(i);
> }
>
> takes about 5 microseconds per call to Sprint().  In contrast, using a
> specialized version of SprintInt (stolen from the print.go code for
> printing integers):

This is fair: fmt.Sprintf is not cheap, but printf isn't always cheap
in C either.
A lot of the overhead is garbage collection and memory management;
some more is naive buffer management in printf. I think Sprint will
get faster as the runtime and libraries improve.

All that said, there's a strconv.Itoa that would be a more direct
choice if your job really was to convert 100k integers.

Russ

dga

unread,
Nov 14, 2009, 9:10:57 PM11/14/09
to golang-nuts
On Nov 14, 8:42 pm, Russ Cox <r...@golang.org> wrote:
> > A consequence of this - using reflection combined with a lot of run-
> > time evaluation of the type and dispatching to various formatting
> > routines - is that something simple, like generating a list of labels:
>
> > for i := 0; i < 100000; i++ {
> >  out <- Sprint(i);
> > }
>
> > takes about 5 microseconds per call to Sprint().  In contrast, using a
> > specialized version of SprintInt (stolen from the print.go code for
> > printing integers):
>
> This is fair: fmt.Sprintf is not cheap, but printf isn't always cheap
> in C either.
> A lot of the overhead is garbage collection and memory management;
> some more is naive buffer management in printf.  I think Sprint will
> get faster as the runtime and libraries improve.

Almost certainly - I think it's not fair to nitpick about the little
details of performance. What I actually was suggesting is that
there's an opportunity to make life simpler for the programmer (who
only needs to know about Sprint), while providing performance equal to
that achieved by the programmer who's willing to specialize their code
to Itoa. Without that kind of specialization, the more general sprintf
()-type routines will always suffer some performance penalty for their
generality.

Now, I'm not sure I'm claiming that's worth mucking up the
language. :) Just throwing out one case I've encountered where it
seemed to make a difference.

> All that said, there's a strconv.Itoa that would be a more direct
> choice if your job really was to convert 100k integers.

Thanks. :) It was for a while. I've decided that, hey, since I have
a nice type system and typed channels, I'm just going to let the
programmer specify what types they want to use in the struct, so those
ints can go back to being ints.

Fun language to play with, btw. The channels and goroutines are a
blast to use. The containers not so much, but I'll give that
time. :-)

-Dave
Reply all
Reply to author
Forward
0 new messages