Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit f011f92

Browse files
committedMar 22, 2013
auto merge of #5463 : alexcrichton/rust/faster-fmt, r=graydon
This is a minor step towards #3571, although I'm sure there's still more work to be done. Previously, `fmt!` collected a bunch of strings in a vector and then called `str::concat`. This changes the behavior by maintaining only one buffer and appending directly into that buffer. This avoids doubly-allocating memory, and it has the added bonus of reducing some allocations in `core::unstable::extfmt` One of the unfortunate side effects of this is that the `rt` module in `extfmt.rs` had to be duplicated to avoid `stage0` errors. Dealing with the change in conversion functions may require a bit of a dance when a snapshot happens, but I think it's doable. If the second speedup commit isn't deemed necessary, I got about a 15% speedup with just the first patch which doesn't require any modification of `extfmt.rs`, so no snapshot weirdness. Here's some other things I ran into when looking at `fmt!`: * I don't think that #2249 is relevant any more except for maybe removing one of `%i` or `%d` * I'm not sure what was in mind for using traits with #3571, but I thought that formatters like `%u` could invoke the `to_uint()` method on the `NumCast` trait, but I ran into some problems like those in #5462 I'm having trouble thinking of other wins for `fmt!`, but if there's some suggestions I'd be more than willing to look into if they'd work out or not.
2 parents 1616ffd + e93654c commit f011f92

File tree

6 files changed

+281
-61
lines changed

6 files changed

+281
-61
lines changed
 

‎src/libcore/unstable/extfmt.rs

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,215 @@ pub mod ct {
470470
// decisions made a runtime. If it proves worthwhile then some of these
471471
// conditions can be evaluated at compile-time. For now though it's cleaner to
472472
// implement it this way, I think.
473+
#[cfg(stage1)]
474+
#[cfg(stage2)]
475+
#[cfg(stage3)]
476+
#[doc(hidden)]
477+
pub mod rt {
478+
use float;
479+
use str;
480+
use sys;
481+
use int;
482+
use uint;
483+
use vec;
484+
use option::{Some, None, Option};
485+
486+
pub const flag_none : u32 = 0u32;
487+
pub const flag_left_justify : u32 = 0b00000000000001u32;
488+
pub const flag_left_zero_pad : u32 = 0b00000000000010u32;
489+
pub const flag_space_for_sign : u32 = 0b00000000000100u32;
490+
pub const flag_sign_always : u32 = 0b00000000001000u32;
491+
pub const flag_alternate : u32 = 0b00000000010000u32;
492+
493+
pub enum Count { CountIs(uint), CountImplied, }
494+
495+
pub enum Ty { TyDefault, TyBits, TyHexUpper, TyHexLower, TyOctal, }
496+
497+
pub struct Conv {
498+
flags: u32,
499+
width: Count,
500+
precision: Count,
501+
ty: Ty,
502+
}
503+
504+
pub pure fn conv_int(cv: Conv, i: int, buf: &mut ~str) {
505+
let radix = 10;
506+
let prec = get_int_precision(cv);
507+
let mut s : ~str = uint_to_str_prec(int::abs(i) as uint, radix, prec);
508+
509+
let head = if i >= 0 {
510+
if have_flag(cv.flags, flag_sign_always) {
511+
Some('+')
512+
} else if have_flag(cv.flags, flag_space_for_sign) {
513+
Some(' ')
514+
} else {
515+
None
516+
}
517+
} else { Some('-') };
518+
unsafe { pad(cv, s, head, PadSigned, buf) };
519+
}
520+
pub pure fn conv_uint(cv: Conv, u: uint, buf: &mut ~str) {
521+
let prec = get_int_precision(cv);
522+
let mut rs =
523+
match cv.ty {
524+
TyDefault => uint_to_str_prec(u, 10, prec),
525+
TyHexLower => uint_to_str_prec(u, 16, prec),
526+
TyHexUpper => str::to_upper(uint_to_str_prec(u, 16, prec)),
527+
TyBits => uint_to_str_prec(u, 2, prec),
528+
TyOctal => uint_to_str_prec(u, 8, prec)
529+
};
530+
unsafe { pad(cv, rs, None, PadUnsigned, buf) };
531+
}
532+
pub pure fn conv_bool(cv: Conv, b: bool, buf: &mut ~str) {
533+
let s = if b { "true" } else { "false" };
534+
// run the boolean conversion through the string conversion logic,
535+
// giving it the same rules for precision, etc.
536+
conv_str(cv, s, buf);
537+
}
538+
pub pure fn conv_char(cv: Conv, c: char, buf: &mut ~str) {
539+
unsafe { pad(cv, "", Some(c), PadNozero, buf) };
540+
}
541+
pub pure fn conv_str(cv: Conv, s: &str, buf: &mut ~str) {
542+
// For strings, precision is the maximum characters
543+
// displayed
544+
let mut unpadded = match cv.precision {
545+
CountImplied => s,
546+
CountIs(max) => if (max as uint) < str::char_len(s) {
547+
str::slice(s, 0, max as uint)
548+
} else {
549+
s
550+
}
551+
};
552+
unsafe { pad(cv, unpadded, None, PadNozero, buf) };
553+
}
554+
pub pure fn conv_float(cv: Conv, f: float, buf: &mut ~str) {
555+
let (to_str, digits) = match cv.precision {
556+
CountIs(c) => (float::to_str_exact, c as uint),
557+
CountImplied => (float::to_str_digits, 6u)
558+
};
559+
let mut s = unsafe { to_str(f, digits) };
560+
let head = if 0.0 <= f {
561+
if have_flag(cv.flags, flag_sign_always) {
562+
Some('+')
563+
} else if have_flag(cv.flags, flag_space_for_sign) {
564+
Some(' ')
565+
} else {
566+
None
567+
}
568+
} else { None };
569+
unsafe { pad(cv, s, head, PadFloat, buf) };
570+
}
571+
pub pure fn conv_poly<T>(cv: Conv, v: &T, buf: &mut ~str) {
572+
let s = sys::log_str(v);
573+
conv_str(cv, s, buf);
574+
}
575+
576+
// Convert a uint to string with a minimum number of digits. If precision
577+
// is 0 and num is 0 then the result is the empty string. Could move this
578+
// to uint: but it doesn't seem all that useful.
579+
pub pure fn uint_to_str_prec(num: uint, radix: uint,
580+
prec: uint) -> ~str {
581+
return if prec == 0u && num == 0u {
582+
~""
583+
} else {
584+
let s = uint::to_str_radix(num, radix);
585+
let len = str::char_len(s);
586+
if len < prec {
587+
let diff = prec - len;
588+
let pad = str::from_chars(vec::from_elem(diff, '0'));
589+
pad + s
590+
} else { s }
591+
};
592+
}
593+
pub pure fn get_int_precision(cv: Conv) -> uint {
594+
return match cv.precision {
595+
CountIs(c) => c as uint,
596+
CountImplied => 1u
597+
};
598+
}
599+
600+
#[deriving(Eq)]
601+
pub enum PadMode { PadSigned, PadUnsigned, PadNozero, PadFloat }
602+
603+
pub fn pad(cv: Conv, mut s: &str, head: Option<char>, mode: PadMode,
604+
buf: &mut ~str) {
605+
let headsize = match head { Some(_) => 1, _ => 0 };
606+
let uwidth : uint = match cv.width {
607+
CountImplied => {
608+
for head.each |&c| {
609+
buf.push_char(c);
610+
}
611+
return buf.push_str(s);
612+
}
613+
CountIs(width) => { width as uint }
614+
};
615+
let strlen = str::char_len(s) + headsize;
616+
if uwidth <= strlen {
617+
for head.each |&c| {
618+
buf.push_char(c);
619+
}
620+
return buf.push_str(s);
621+
}
622+
let mut padchar = ' ';
623+
let diff = uwidth - strlen;
624+
if have_flag(cv.flags, flag_left_justify) {
625+
for head.each |&c| {
626+
buf.push_char(c);
627+
}
628+
buf.push_str(s);
629+
for diff.times {
630+
buf.push_char(padchar);
631+
}
632+
return;
633+
}
634+
let (might_zero_pad, signed) = match mode {
635+
PadNozero => (false, true),
636+
PadSigned => (true, true),
637+
PadFloat => (true, true),
638+
PadUnsigned => (true, false)
639+
};
640+
pure fn have_precision(cv: Conv) -> bool {
641+
return match cv.precision { CountImplied => false, _ => true };
642+
}
643+
let zero_padding = {
644+
if might_zero_pad && have_flag(cv.flags, flag_left_zero_pad) &&
645+
(!have_precision(cv) || mode == PadFloat) {
646+
padchar = '0';
647+
true
648+
} else {
649+
false
650+
}
651+
};
652+
let padstr = str::from_chars(vec::from_elem(diff, padchar));
653+
// This is completely heinous. If we have a signed value then
654+
// potentially rip apart the intermediate result and insert some
655+
// zeros. It may make sense to convert zero padding to a precision
656+
// instead.
657+
658+
if signed && zero_padding {
659+
for head.each |&head| {
660+
if head == '+' || head == '-' || head == ' ' {
661+
buf.push_char(head);
662+
buf.push_str(padstr);
663+
buf.push_str(s);
664+
return;
665+
}
666+
}
667+
}
668+
buf.push_str(padstr);
669+
for head.each |&c| {
670+
buf.push_char(c);
671+
}
672+
buf.push_str(s);
673+
}
674+
#[inline(always)]
675+
pub pure fn have_flag(flags: u32, f: u32) -> bool {
676+
flags & f != 0
677+
}
678+
}
679+
680+
// XXX: remove after a snapshot of the above changes have gone in
681+
#[cfg(stage0)]
473682
#[doc(hidden)]
474683
pub mod rt {
475684
use float;

‎src/librustc/middle/typeck/check/regionck.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ use core::prelude::*;
3232
use middle::freevars::get_freevars;
3333
use middle::pat_util::{pat_bindings, pat_is_binding};
3434
use middle::ty::{encl_region, re_scope};
35-
use middle::ty::{vstore_box, vstore_fixed, vstore_slice};
3635
use middle::ty;
3736
use middle::typeck::check::FnCtxt;
3837
use middle::typeck::check::lookup_def;

‎src/librustc/middle/typeck/check/regionmanip.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ use util::ppaux::region_to_str;
2020
use util::ppaux;
2121

2222
use std::list::Cons;
23-
use syntax::ast;
24-
use syntax::codemap;
2523

2624
// Helper functions related to manipulating region types.
2725

‎src/librustc/middle/typeck/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ independently:
5151
use core::prelude::*;
5252

5353
use middle::resolve;
54-
use middle::ty::{ty_param_substs_and_ty, vstore_uniq};
5554
use middle::ty;
5655
use util::common::time;
5756
use util::ppaux;

‎src/libsyntax/ext/build.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ pub fn mk_access(cx: @ext_ctxt, sp: span, +p: ~[ast::ident], m: ast::ident)
108108
pub fn mk_addr_of(cx: @ext_ctxt, sp: span, e: @ast::expr) -> @ast::expr {
109109
return mk_expr(cx, sp, ast::expr_addr_of(ast::m_imm, e));
110110
}
111+
pub fn mk_mut_addr_of(cx: @ext_ctxt, sp: span, e: @ast::expr) -> @ast::expr {
112+
return mk_expr(cx, sp, ast::expr_addr_of(ast::m_mutbl, e));
113+
}
111114
pub fn mk_call_(cx: @ext_ctxt, sp: span, fn_expr: @ast::expr,
112115
+args: ~[@ast::expr]) -> @ast::expr {
113116
mk_expr(cx, sp, ast::expr_call(fn_expr, args, ast::NoSugar))

‎src/libsyntax/ext/fmt.rs

Lines changed: 69 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -139,19 +139,17 @@ fn pieces_to_expr(cx: @ext_ctxt, sp: span,
139139
make_conv_struct(cx, sp, rt_conv_flags, rt_conv_width,
140140
rt_conv_precision, rt_conv_ty)
141141
}
142-
fn make_conv_call(cx: @ext_ctxt, sp: span, conv_type: ~str, cnv: &Conv,
143-
arg: @ast::expr) -> @ast::expr {
142+
fn make_conv_call(cx: @ext_ctxt, sp: span, conv_type: &str, cnv: &Conv,
143+
arg: @ast::expr, buf: @ast::expr) -> @ast::expr {
144144
let fname = ~"conv_" + conv_type;
145145
let path = make_path_vec(cx, @fname);
146146
let cnv_expr = make_rt_conv_expr(cx, sp, cnv);
147-
let args = ~[cnv_expr, arg];
147+
let args = ~[cnv_expr, arg, buf];
148148
return mk_call_global(cx, arg.span, path, args);
149149
}
150150

151-
fn make_new_conv(cx: @ext_ctxt, sp: span, cnv: &Conv, arg: @ast::expr) ->
152-
@ast::expr {
153-
// FIXME: Move validation code into core::extfmt (Issue #2249)
154-
151+
fn make_new_conv(cx: @ext_ctxt, sp: span, cnv: &Conv,
152+
arg: @ast::expr, buf: @ast::expr) -> @ast::expr {
155153
fn is_signed_type(cnv: &Conv) -> bool {
156154
match cnv.ty {
157155
TyInt(s) => match s {
@@ -198,29 +196,20 @@ fn pieces_to_expr(cx: @ext_ctxt, sp: span,
198196
CountIs(_) => (),
199197
_ => cx.span_unimpl(sp, unsupported)
200198
}
201-
match cnv.ty {
202-
TyStr => return make_conv_call(cx, arg.span, ~"str", cnv, arg),
203-
TyInt(sign) => match sign {
204-
Signed => return make_conv_call(cx, arg.span, ~"int", cnv, arg),
205-
Unsigned => {
206-
return make_conv_call(cx, arg.span, ~"uint", cnv, arg)
207-
}
208-
},
209-
TyBool => return make_conv_call(cx, arg.span, ~"bool", cnv, arg),
210-
TyChar => return make_conv_call(cx, arg.span, ~"char", cnv, arg),
211-
TyHex(_) => {
212-
return make_conv_call(cx, arg.span, ~"uint", cnv, arg);
213-
}
214-
TyBits => return make_conv_call(cx, arg.span, ~"uint", cnv, arg),
215-
TyOctal => return make_conv_call(cx, arg.span, ~"uint", cnv, arg),
216-
TyFloat => {
217-
return make_conv_call(cx, arg.span, ~"float", cnv, arg);
218-
}
219-
TyPoly => return make_conv_call(cx, arg.span, ~"poly", cnv,
220-
mk_addr_of(cx, sp, arg))
221-
}
199+
let (name, actual_arg) = match cnv.ty {
200+
TyStr => ("str", arg),
201+
TyInt(Signed) => ("int", arg),
202+
TyBool => ("bool", arg),
203+
TyChar => ("char", arg),
204+
TyBits | TyOctal | TyHex(_) | TyInt(Unsigned) => ("uint", arg),
205+
TyFloat => ("float", arg),
206+
TyPoly => ("poly", mk_addr_of(cx, sp, arg))
207+
};
208+
return make_conv_call(cx, arg.span, name, cnv, actual_arg,
209+
mk_mut_addr_of(cx, arg.span, buf));
222210
}
223211
fn log_conv(c: &Conv) {
212+
debug!("Building conversion:");
224213
match c.param {
225214
Some(p) => { debug!("param: %s", p.to_str()); }
226215
_ => debug!("param: none")
@@ -268,49 +257,72 @@ fn pieces_to_expr(cx: @ext_ctxt, sp: span,
268257
TyPoly => debug!("type: poly")
269258
}
270259
}
260+
271261
let fmt_sp = args[0].span;
272262
let mut n = 0u;
273-
let mut piece_exprs = ~[];
274263
let nargs = args.len();
275-
for pieces.each |pc| {
276-
match *pc {
277-
PieceString(ref s) => {
278-
piece_exprs.push(mk_uniq_str(cx, fmt_sp, copy *s))
279-
}
280-
PieceConv(ref conv) => {
281-
n += 1u;
282-
if n >= nargs {
283-
cx.span_fatal(sp,
284-
~"not enough arguments to fmt! " +
264+
265+
/* 'ident' is the local buffer building up the result of fmt! */
266+
let ident = cx.parse_sess().interner.intern(@~"__fmtbuf");
267+
let buf = || mk_path(cx, fmt_sp, ~[ident]);
268+
let str_ident = cx.parse_sess().interner.intern(@~"str");
269+
let push_ident = cx.parse_sess().interner.intern(@~"push_str");
270+
let mut stms = ~[];
271+
272+
/* Translate each piece (portion of the fmt expression) by invoking the
273+
corresponding function in core::unstable::extfmt. Each function takes a
274+
buffer to insert data into along with the data being formatted. */
275+
do vec::consume(pieces) |i, pc| {
276+
match pc {
277+
/* Raw strings get appended via str::push_str */
278+
PieceString(s) => {
279+
let portion = mk_uniq_str(cx, fmt_sp, s);
280+
281+
/* If this is the first portion, then initialize the local
282+
buffer with it directly */
283+
if i == 0 {
284+
stms.push(mk_local(cx, fmt_sp, true, ident, portion));
285+
} else {
286+
let args = ~[mk_mut_addr_of(cx, fmt_sp, buf()), portion];
287+
let call = mk_call_global(cx,
288+
fmt_sp,
289+
~[str_ident, push_ident],
290+
args);
291+
stms.push(mk_stmt(cx, fmt_sp, call));
292+
}
293+
}
294+
295+
/* Invoke the correct conv function in extfmt */
296+
PieceConv(ref conv) => {
297+
n += 1u;
298+
if n >= nargs {
299+
cx.span_fatal(sp,
300+
~"not enough arguments to fmt! " +
285301
~"for the given format string");
302+
}
303+
304+
log_conv(conv);
305+
/* If the first portion is a conversion, then the local buffer
306+
must be initialized as an empty string */
307+
if i == 0 {
308+
stms.push(mk_local(cx, fmt_sp, true, ident,
309+
mk_uniq_str(cx, fmt_sp, ~"")));
310+
}
311+
stms.push(mk_stmt(cx, fmt_sp,
312+
make_new_conv(cx, fmt_sp, conv,
313+
args[n], buf())));
286314
}
287-
debug!("Building conversion:");
288-
log_conv(conv);
289-
let arg_expr = args[n];
290-
let c_expr = make_new_conv(
291-
cx,
292-
fmt_sp,
293-
conv,
294-
arg_expr
295-
);
296-
piece_exprs.push(c_expr);
297-
}
298315
}
299316
}
300-
let expected_nargs = n + 1u; // n conversions + the fmt string
301317

318+
let expected_nargs = n + 1u; // n conversions + the fmt string
302319
if expected_nargs < nargs {
303320
cx.span_fatal
304321
(sp, fmt!("too many arguments to fmt!. found %u, expected %u",
305322
nargs, expected_nargs));
306323
}
307324

308-
let arg_vec = mk_fixed_vec_e(cx, fmt_sp, piece_exprs);
309-
return mk_call_global(cx,
310-
fmt_sp,
311-
~[cx.parse_sess().interner.intern(@~"str"),
312-
cx.parse_sess().interner.intern(@~"concat")],
313-
~[arg_vec]);
325+
return mk_block(cx, fmt_sp, ~[], stms, Some(buf()));
314326
}
315327
//
316328
// Local Variables:

0 commit comments

Comments
 (0)
Please sign in to comment.