Restructuring and various optimisations to drastically improve appendFormat:

performance.


git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@19307 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
CaS 2004-05-14 10:52:30 +00:00
parent 80741506f0
commit 35b7fbf96e
8 changed files with 677 additions and 761 deletions

View file

@ -1,3 +1,19 @@
2004-05-14 Richard Frith-Macdonald <rfm@gnu.org>
* Source/GSFormat.h:
* Source/GSFormat.m:
* Source/GSPrivate.h:
* Source/GSString.m:
* Source/GSeq.h:
* Source/NSString.m:
Updates to allow GSFormat to write directly into an existing string
in either 16bit or 8bit format. Also tidyups and modifications to
use temporary buffers on stack rather than malloc/free as long as
the buffers are reasonably sized.
* Source/benchmark.m: Trivial string format benchmarks ...
Performance improvement of 10% for initWithFormat and 90% for
appendFormat operations.
2004-05-13 Richard Frith-Macdonald <rfm@gnu.org> 2004-05-13 Richard Frith-Macdonald <rfm@gnu.org>
* Source/NSCalendarDate.m: ([initWithString:calendarFormat:locale:]) * Source/NSCalendarDate.m: ([initWithString:calendarFormat:locale:])

View file

@ -26,18 +26,12 @@
#define __GSFormat_H_ #define __GSFormat_H_
#include <Foundation/NSZone.h> #include <Foundation/NSZone.h>
#include "GSPrivate.h"
@class NSDictionary; @class NSDictionary;
typedef struct {
unichar *buf;
size_t len;
size_t size;
NSZone *z;
} FormatBuf_t;
void void
GSFormat(FormatBuf_t *fb, const unichar *fmt, va_list ap, NSDictionary *loc); GSFormat(GSStr fb, const unichar *fmt, va_list ap, NSDictionary *loc);
#endif #endif

View file

@ -127,7 +127,7 @@ struct printf_info
}; };
/* Type of a printf specifier-handler function. /* Type of a printf specifier-handler function.
STREAM is the FormatBuf_t on which to write output. STREAM is the GSStr on which to write output.
INFO gives information about the format specification. INFO gives information about the format specification.
ARGS is a vector of pointers to the argument data; ARGS is a vector of pointers to the argument data;
the number of pointers will be the number returned the number of pointers will be the number returned
@ -733,32 +733,8 @@ parse_one_spec (const unichar *format, size_t posn, struct printf_spec *spec,
} }
#define outchar(Ch) \ #define outchar(Ch) GSStrAppendUnichar(s, Ch)
do \ #define outstring(String, Len) GSStrAppendUnichars(s, String, Len)
{ \
register const wint_t outc = (Ch); \
if (s->len+1 >= s->size) { \
s->size += s->size / 2; \
s->buf = NSZoneRealloc(s->z, s->buf, s->size*sizeof(s->buf[0])); \
} \
s->buf[s->len++] = outc; \
++done; \
} \
while (0)
#define outstring(String, Len) \
do \
{ \
unsigned i; \
\
if (s->len+((unsigned)(Len)) >= s->size) { \
s->size += s->size/2 > ((unsigned)(Len))? s->size/2: (unsigned)(Len); \
s->buf = NSZoneRealloc(s->z, s->buf, s->size*sizeof(s->buf[0])); \
} \
for (i=0; i < ((unsigned)(Len)); i++) s->buf[s->len++] = (String)[i]; \
done += (unsigned)(Len); \
} \
while (0)
/* For handling long_double and longlong we use the same flag. If /* For handling long_double and longlong we use the same flag. If
`long' and `long long' are effectively the same type define it to `long' and `long long' are effectively the same type define it to
@ -787,7 +763,7 @@ static const unichar null[] = {'(','n','u','l','l',')','\0'};
/* Handle unknown format specifier. */ /* Handle unknown format specifier. */
static int printf_unknown (FormatBuf_t *, const struct printf_info *, static int printf_unknown (GSStr, const struct printf_info *,
const void *const *); const void *const *);
/* Group digits of number string. */ /* Group digits of number string. */
@ -796,7 +772,7 @@ static unichar *group_number (unichar *, unichar *, const char *, NSString *);
/* The function itself. */ /* The function itself. */
void void
GSFormat (FormatBuf_t *s, const unichar *format, va_list ap, GSFormat (GSStr s, const unichar *format, va_list ap,
NSDictionary *locale) NSDictionary *locale)
{ {
/* The character used as thousands separator. */ /* The character used as thousands separator. */
@ -866,12 +842,6 @@ NSDictionary *locale)
#define CHAR_CLASS(Ch) (jump_table[(wint_t) (Ch) - ' ']) #define CHAR_CLASS(Ch) (jump_table[(wint_t) (Ch) - ' '])
# define JUMP_TABLE_TYPE const void *const # define JUMP_TABLE_TYPE const void *const
if (s->size == 0)
{
s->buf = NSZoneMalloc(s->z, 100*sizeof(unichar));
s->size = 100;
}
/* Initialize local variables. */ /* Initialize local variables. */
done = 0; done = 0;
grouping = (const char *) -1; grouping = (const char *) -1;
@ -1768,7 +1738,7 @@ NSDictionary *locale)
/* Allocate dynamically an array which definitely is long /* Allocate dynamically an array which definitely is long
enough for the wide character version. */ enough for the wide character version. */
if (len < 8192 if (len < 8192
|| ((string = (unichar *) NSZoneMalloc(s->z, len * sizeof (unichar))) || ((string = (unichar *) NSZoneMalloc(s->_zone, len * sizeof (unichar)))
== NULL)) == NULL))
string = (unichar *) alloca (len * sizeof (unichar)); string = (unichar *) alloca (len * sizeof (unichar));
else else
@ -1795,7 +1765,7 @@ NSDictionary *locale)
/* Allocate dynamically an array which definitely is long /* Allocate dynamically an array which definitely is long
enough for the wide character version. */ enough for the wide character version. */
if (len < 8192 if (len < 8192
|| ((string = (unichar *) NSZoneMalloc(s->z, len * sizeof (unichar))) || ((string = (unichar *) NSZoneMalloc(s->_zone, len * sizeof (unichar)))
== NULL)) == NULL))
string = (unichar *) alloca (len * sizeof (unichar)); string = (unichar *) alloca (len * sizeof (unichar));
else else
@ -1819,7 +1789,7 @@ NSDictionary *locale)
PAD (' '); PAD (' ');
} }
if (string_malloced) if (string_malloced)
NSZoneFree(s->z, string); NSZoneFree(s->_zone, string);
} }
break; break;
@ -1851,7 +1821,7 @@ NSDictionary *locale)
/* Allocate dynamically an array which definitely is long /* Allocate dynamically an array which definitely is long
enough for the wide character version. */ enough for the wide character version. */
if (len < 8192 if (len < 8192
|| ((string = (unichar *) NSZoneMalloc(s->z, len * sizeof (unichar))) || ((string = (unichar *) NSZoneMalloc(s->_zone, len * sizeof (unichar)))
== NULL)) == NULL))
string = (unichar *) alloca (len * sizeof (unichar)); string = (unichar *) alloca (len * sizeof (unichar));
else else
@ -1875,7 +1845,7 @@ NSDictionary *locale)
PAD (' '); PAD (' ');
} }
if (string_malloced) if (string_malloced)
NSZoneFree(s->z, string); NSZoneFree(s->_zone, string);
} }
break; break;
@ -1919,7 +1889,7 @@ all_done:
/* Handle an unknown format specifier. This prints out a canonicalized /* Handle an unknown format specifier. This prints out a canonicalized
representation of the format spec itself. */ representation of the format spec itself. */
static int static int
printf_unknown (FormatBuf_t *s, const struct printf_info *info, printf_unknown (GSStr s, const struct printf_info *info,
const void *const *args) const void *const *args)
{ {

View file

@ -106,6 +106,42 @@ GS_EXPORT BOOL GSIsByteEncoding(NSStringEncoding encoding);
} }
@end @end
/*
* GSMutableString - concrete mutable string, capable of changing its storage
* from holding 8-bit to 16-bit character set.
*/
@interface GSMutableString : NSMutableString
{
union {
unichar *u;
unsigned char *c;
} _contents;
unsigned int _count;
struct {
unsigned int wide: 1;
unsigned int free: 1;
unsigned int unused: 2;
unsigned int hash: 28;
} _flags;
NSZone *_zone;
unsigned int _capacity;
}
@end
/*
* Typedef for access to internals of concrete string objects.
*/
typedef struct {
@defs(GSMutableString)
} GSStr_t;
typedef GSStr_t *GSStr;
/*
* Functions to append to GSStr
*/
extern void GSStrAppendUnichar(GSStr s, unichar);
extern void GSStrAppendUnichars(GSStr s, const unichar *u, unsigned l);
/* /*
* Enumeration for MacOS-X compatibility user defaults settings. * Enumeration for MacOS-X compatibility user defaults settings.
* For efficiency, we save defaults information which is used by the * For efficiency, we save defaults information which is used by the

File diff suppressed because it is too large Load diff

View file

@ -290,14 +290,14 @@ static inline void GSeq_uppercase(GSeq seq)
* Set up macros for dealing with 'self' on the basis of GSQ_S * Set up macros for dealing with 'self' on the basis of GSQ_S
*/ */
#if GSEQ_S == GSEQ_US #if GSEQ_S == GSEQ_US
#define GSEQ_ST ivars #define GSEQ_ST GSStr
#define GSEQ_SLEN s->_count #define GSEQ_SLEN s->_count
#define GSEQ_SGETC(I) s->_contents.u[I] #define GSEQ_SGETC(I) s->_contents.u[I]
#define GSEQ_SGETR(B,R) memcpy(B, &s->_contents.u[R.location], 2*(R).length) #define GSEQ_SGETR(B,R) memcpy(B, &s->_contents.u[R.location], 2*(R).length)
#define GSEQ_SRANGE(I) (*srImp)((id)s, ranSel, I) #define GSEQ_SRANGE(I) (*srImp)((id)s, ranSel, I)
#else #else
#if GSEQ_S == GSEQ_CS #if GSEQ_S == GSEQ_CS
#define GSEQ_ST ivars #define GSEQ_ST GSStr
#define GSEQ_SLEN s->_count #define GSEQ_SLEN s->_count
#define GSEQ_SGETC(I) (unichar)s->_contents.c[I] #define GSEQ_SGETC(I) (unichar)s->_contents.c[I]
#define GSEQ_SGETR(B,R) ( { \ #define GSEQ_SGETR(B,R) ( { \
@ -322,14 +322,14 @@ static inline void GSeq_uppercase(GSeq seq)
* Set up macros for dealing with 'other' string on the basis of GSQ_O * Set up macros for dealing with 'other' string on the basis of GSQ_O
*/ */
#if GSEQ_O == GSEQ_US #if GSEQ_O == GSEQ_US
#define GSEQ_OT ivars #define GSEQ_OT GSStr
#define GSEQ_OLEN o->_count #define GSEQ_OLEN o->_count
#define GSEQ_OGETC(I) o->_contents.u[I] #define GSEQ_OGETC(I) o->_contents.u[I]
#define GSEQ_OGETR(B,R) memcpy(B, &o->_contents.u[R.location], 2*(R).length) #define GSEQ_OGETR(B,R) memcpy(B, &o->_contents.u[R.location], 2*(R).length)
#define GSEQ_ORANGE(I) (*orImp)((id)o, ranSel, I) #define GSEQ_ORANGE(I) (*orImp)((id)o, ranSel, I)
#else #else
#if GSEQ_O == GSEQ_CS #if GSEQ_O == GSEQ_CS
#define GSEQ_OT ivars #define GSEQ_OT GSStr
#define GSEQ_OLEN o->_count #define GSEQ_OLEN o->_count
#define GSEQ_OGETC(I) (unichar)o->_contents.c[I] #define GSEQ_OGETC(I) (unichar)o->_contents.c[I]
#define GSEQ_OGETR(B,R) ( { \ #define GSEQ_OGETR(B,R) ( { \

View file

@ -69,6 +69,7 @@
// For private method _decodePropertyListForKey: // For private method _decodePropertyListForKey:
#include "Foundation/NSKeyedArchiver.h" #include "Foundation/NSKeyedArchiver.h"
#include "GNUstepBase/GSMime.h" #include "GNUstepBase/GSMime.h"
#include "GSPrivate.h"
#include "GSFormat.h" #include "GSFormat.h"
#include <limits.h> #include <limits.h>
#include <sys/stat.h> #include <sys/stat.h>
@ -1153,321 +1154,70 @@ handle_printf_atsign (FILE *stream,
locale: (NSDictionary*)locale locale: (NSDictionary*)locale
arguments: (va_list)argList arguments: (va_list)argList
{ {
FormatBuf_t f; unsigned char buf[2048];
unichar *fmt; GSStr_t f;
unichar fbuf[1024];
unichar *fmt = fbuf;
size_t len; size_t len;
/*
* First we provide an array of unichar characters containing the
* format string. For performance reasons we try to use an on-stack
* buffer if the format string is small enough ... it almost always
* will be.
*/
len = [format length]; len = [format length];
fmt = objc_malloc((len+1)*sizeof(unichar)); if (len >= 1024)
{
fmt = objc_malloc((len+1)*sizeof(unichar));
}
[format getCharacters: fmt]; [format getCharacters: fmt];
fmt[len] = '\0'; fmt[len] = '\0';
f.z = NSDefaultMallocZone();
f.buf = NSZoneMalloc(f.z, 100*sizeof(unichar)); /*
f.len = 0; * Now set up 'f' as a GSMutableString object whose initial buffer is
f.size = 100; * allocated on the stack. The GSFormat function can write into it.
*/
f.isa = GSMutableStringClass;
f._zone = NSDefaultMallocZone();
f._contents.c = buf;
f._capacity = sizeof(buf);
f._count = 0;
f._flags.wide = 0;
f._flags.free = 0;
GSFormat(&f, fmt, argList, locale); GSFormat(&f, fmt, argList, locale);
objc_free(fmt); if (fmt != fbuf)
// don't use noCopy because f.size > f.len!
self = [self initWithCharacters: f.buf length: f.len];
NSZoneFree(f.z, f.buf);
return self;
}
#if 0
/* xxx Change this when we have non-CString classes */
- (id) initWithFormat: (NSString*)format
locale: (NSDictionary*)locale
arguments: (va_list)argList
{
#if defined(HAVE_VSPRINTF) || defined(HAVE_VASPRINTF)
const char *format_cp = [format lossyCString];
int format_len = strlen (format_cp);
#ifdef HAVE_VASPRINTF
char *buf;
int printed_len = 0;
NSString *ret;
#ifndef HAVE_REGISTER_PRINTF_FUNCTION
NSZone *z = GSObjCZone(self);
/* If the available libc doesn't have `register_printf_function()', then
the `%@' printf directive isn't available with printf() and friends.
Here we make a feable attempt to handle it. */
{
/* We need a local copy since we change it. (Changing and undoing
the change doesn't work because some format strings are constant
strings, placed in a non-writable section of the executable, and
writing to them will cause a segfault.) */
char format_cp_copy[format_len+1];
char *atsign_pos; /* points to a location inside format_cp_copy */
char *format_to_go = format_cp_copy;
char *buf_l;
#define _PRINTF_BUF_LEN 256
int printed_local_len, avail_len = _PRINTF_BUF_LEN;
int cstring_len;
buf = NSZoneMalloc(z, _PRINTF_BUF_LEN);
strcpy (format_cp_copy, format_cp);
/* Loop once for each `%@' in the format string. */
while ((atsign_pos = strstr (format_to_go, "%@")))
{
const char *cstring;
char *formatter_pos; // Position for formatter.
/* If there is a "%%@", then do the right thing: print it literally. */
if ((*(atsign_pos-1) == '%')
&& atsign_pos != format_cp_copy)
continue;
/* Temporarily terminate the string before the `%@'. */
*atsign_pos = '\0';
/* Print the part before the '%@' */
printed_local_len = VASPRINTF_LENGTH (vasprintf (&buf_l,
format_to_go, argList));
if(buf_l)
{
if(avail_len < printed_local_len+1)
{
NS_DURING
{
buf = NSZoneRealloc(z, buf,
printed_len+printed_local_len+_PRINTF_BUF_LEN);
avail_len += _PRINTF_BUF_LEN;
}
NS_HANDLER
{
free(buf_l);
[localException raise];
}
NS_ENDHANDLER
}
memcpy(&buf[printed_len], buf_l, printed_local_len+1);
avail_len -= printed_local_len;
printed_len += printed_local_len;
free(buf_l);
}
else
{
[NSException raise: NSMallocException
format: @"No available memory"];
}
/* Skip arguments used in last vsprintf(). */
while ((formatter_pos = strchr(format_to_go, '%')))
{
char *spec_pos; // Position of conversion specifier.
if (*(formatter_pos+1) == '%')
{
format_to_go = formatter_pos+2;
continue;
}
spec_pos = strpbrk(formatter_pos+1, "dioxXucsfeEgGpn\0");
switch (*spec_pos)
{
#ifndef powerpc
/* FIXME: vsprintf on powerpc apparently advances the arg list
so this doesn't need to be done. Make a more general check
for this */
case 'd': case 'i': case 'o':
case 'x': case 'X': case 'u': case 'c':
va_arg(argList, int);
break;
case 's':
if (*(spec_pos - 1) == '*')
va_arg(argList, int*);
va_arg(argList, char*);
break;
case 'f': case 'e': case 'E': case 'g': case 'G':
va_arg(argList, double);
break;
case 'p':
va_arg(argList, void*);
break;
case 'n':
va_arg(argList, int*);
break;
#endif /* NOT powerpc */
case '\0':
spec_pos--;
break;
}
format_to_go = spec_pos+1;
}
/* Get a C-string (char*) from the String object, and print it. */
cstring = [[(id) va_arg (argList, id) description] lossyCString];
if (!cstring)
cstring = "<null string>";
cstring_len = strlen(cstring);
if(cstring_len)
{
if(avail_len < cstring_len+1)
{
buf = NSZoneRealloc(z, buf,
printed_len+cstring_len+_PRINTF_BUF_LEN);
avail_len += _PRINTF_BUF_LEN;
}
memcpy(&buf[printed_len], cstring, cstring_len+1);
avail_len -= cstring_len;
printed_len += cstring_len;
}
/* Skip over this `%@', and look for another one. */
format_to_go = atsign_pos + 2;
}
/* Print the rest of the string after the last `%@'. */
printed_local_len = VASPRINTF_LENGTH (vasprintf (&buf_l,
format_to_go, argList));
if(buf_l)
{
if(avail_len < printed_local_len+1)
{
NS_DURING
{
buf = NSZoneRealloc(z, buf,
printed_len+printed_local_len+_PRINTF_BUF_LEN);
avail_len += _PRINTF_BUF_LEN;
}
NS_HANDLER
{
free(buf_l);
[localException raise];
}
NS_ENDHANDLER
}
memcpy(&buf[printed_len], buf_l, printed_local_len+1);
avail_len -= printed_local_len;
printed_len += printed_local_len;
free(buf_l);
}
else
{
[NSException raise: NSMallocException
format: @"No available memory"];
}
}
#else /* HAVE_VSPRINTF */
/* The available libc has `register_printf_function()', so the `%@'
printf directive is handled by printf and friends. */
printed_len = VASPRINTF_LENGTH (vasprintf (&buf, format_cp, argList));
if(!buf)
{ {
[NSException raise: NSMallocException objc_free(fmt);
format: @"No available memory"];
} }
#endif /* !HAVE_REGISTER_PRINTF_FUNCTION */
ret = [self initWithCString: buf]; /*
#ifndef HAVE_REGISTER_PRINTF_FUNCTION * Don't use noCopy because f._contents.u may be memory on the stack,
NSZoneFree(z, buf); * and even if it wasn't f._capacity may be greater than f._count so
#else * we could be wasting quite a bit of space. Better to accept a
free(buf); * performance hit due to copying data (and allocating/deallocating
#endif * the temporary buffer) for large strings. For most strings, the
return ret; * on-stack memory will have been used, so we will get better performance.
#else */
/* xxx horrible disgusting BUFFER_EXTRA arbitrary limit; fix this! */ if (f._flags.wide == 1)
#define BUFFER_EXTRA 1024*500 {
char buf[format_len + BUFFER_EXTRA]; self = [self initWithCharacters: f._contents.u length: f._count];
int printed_len = 0; }
else
{
self = [self initWithCString: f._contents.c length: f._count];
}
#ifndef HAVE_REGISTER_PRINTF_FUNCTION /*
/* If the available libc doesn't have `register_printf_function()', then * If the string had to grow beyond the initial buffer size, we must
the `%@' printf directive isn't available with printf() and friends. * release any allocated memory.
Here we make a feable attempt to handle it. */ */
{ if (f._flags.free == 1)
/* We need a local copy since we change it. (Changing and undoing {
the change doesn't work because some format strings are constant NSZoneFree(f._zone, f._contents.c);
strings, placed in a non-writable section of the executable, and }
writing to them will cause a segfault.) */
char format_cp_copy[format_len+1];
char *atsign_pos; /* points to a location inside format_cp_copy */
char *format_to_go = format_cp_copy;
strcpy (format_cp_copy, format_cp);
/* Loop once for each `%@' in the format string. */
while ((atsign_pos = strstr (format_to_go, "%@")))
{
const char *cstring;
char *formatter_pos; // Position for formatter.
/* If there is a "%%@", then do the right thing: print it literally. */
if ((*(atsign_pos-1) == '%')
&& atsign_pos != format_cp_copy)
continue;
/* Temporarily terminate the string before the `%@'. */
*atsign_pos = '\0';
/* Print the part before the '%@' */
printed_len += VSPRINTF_LENGTH (vsprintf (buf+printed_len,
format_to_go, argList));
/* Skip arguments used in last vsprintf(). */
while ((formatter_pos = strchr(format_to_go, '%')))
{
char *spec_pos; // Position of conversion specifier.
if (*(formatter_pos+1) == '%')
{
format_to_go = formatter_pos+2;
continue;
}
spec_pos = strpbrk(formatter_pos+1, "dioxXucsfeEgGpn\0");
switch (*spec_pos)
{
#ifndef powerpc
/* FIXME: vsprintf on powerpc apparently advances the arg list
so this doesn't need to be done. Make a more general check
for this */
case 'd': case 'i': case 'o':
case 'x': case 'X': case 'u': case 'c':
(void)va_arg(argList, int);
break;
case 's':
if (*(spec_pos - 1) == '*')
(void)va_arg(argList, int*);
(void)va_arg(argList, char*);
break;
case 'f': case 'e': case 'E': case 'g': case 'G':
(void)va_arg(argList, double);
break;
case 'p':
(void)va_arg(argList, void*);
break;
case 'n':
(void)va_arg(argList, int*);
break;
#endif /* NOT powerpc */
case '\0':
spec_pos--;
break;
}
format_to_go = spec_pos+1;
}
/* Get a C-string (char*) from the String object, and print it. */
cstring = [[(id) va_arg (argList, id) description] lossyCString];
if (!cstring)
cstring = "<null string>";
strcat (buf+printed_len, cstring);
printed_len += strlen (cstring);
/* Skip over this `%@', and look for another one. */
format_to_go = atsign_pos + 2;
}
/* Print the rest of the string after the last `%@'. */
printed_len += VSPRINTF_LENGTH (vsprintf (buf+printed_len,
format_to_go, argList));
}
#else
/* The available libc has `register_printf_function()', so the `%@'
printf directive is handled by printf and friends. */
printed_len = VSPRINTF_LENGTH (vsprintf (buf, format_cp, argList));
#endif /* !HAVE_REGISTER_PRINTF_FUNCTION */
/* Raise an exception if we overran our buffer. */
NSParameterAssert (printed_len < format_len + BUFFER_EXTRA - 1);
return [self initWithCString: buf];
#endif /* HAVE_VASPRINTF */
#else /* HAVE_VSPRINTF || HAVE_VASPRINTF */
[self notImplemented: _cmd];
return self; return self;
#endif
} }
#endif
/** /**
* Initialises the receiver with the supplied data, using the * Initialises the receiver with the supplied data, using the

View file

@ -371,6 +371,7 @@ bench_str()
{ {
int i; int i;
NSString *str; NSString *str;
NSMutableString *ms;
id plist; id plist;
NSString *plstr; NSString *plstr;
Class arc = [NSArchiver class]; Class arc = [NSArchiver class];
@ -408,6 +409,25 @@ bench_str()
plstr = [plist description]; plstr = [plist description];
printf("NSString\n"); printf("NSString\n");
START_TIMER;
for (i = 0; i < MAX_COUNT; i++)
{
str = [[stringClass alloc] initWithFormat: @"Hello %d", i];
RELEASE(str);
}
END_TIMER;
PRINT_TIMER("NSString (1 initWithFormat:) \t");
ms = [NSMutableString stringWithCapacity: 0];
START_TIMER;
for (i = 0; i < MAX_COUNT; i++)
{
[ms appendFormat: @"%d", i];
}
END_TIMER;
PRINT_TIMER("NSString (1 appendFormat:) \t\t");
START_TIMER; START_TIMER;
for (i = 0; i < MAX_COUNT; i++) for (i = 0; i < MAX_COUNT; i++)
{ {