[Bug 1391510] Re: sscanf extremely slow on large strings
Shahbaz Youssefi
ShabbyX at gmail.com
Tue Nov 11 13:50:46 UTC 2014
** Description changed:
This is a bug on glib, and I'm not sure if it applies upstream too
(although I suspect it does).
The bug is that `sscanf` (`vsscanf` actually) is very slow on large
strings. It _seems_ like `vsscanf` is first trying to find the end of
the string (with `rawmemchr`), which is expensive given that most of the
string is not going to be read.
Here's a test code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#define N 50000
static int _debug_helper(const char *src, int *a, int *n)
{
#if 1
return sscanf(src, "%d%n", a, n);
#else
- char *end;
+ char *end;
- errno = 0;
- *a = strtol(src, &end, 10);
- *n = end - src;
+ errno = 0;
+ *a = strtol(src, &end, 10);
+ *n = end - src;
- return errno == 0 && *n > 0;
+ return errno == 0 && *n > 0;
#endif
}
int main()
{
int i;
int a;
int n;
int so_far = 0;
char *big_string = malloc(N * 4 + 1);
for (i = 0; i < N; ++i)
strcpy(big_string + i * 4, "123 ");
big_string[N * 4] = '\0';
while (1)
{
if (_debug_helper(big_string + so_far, &a, &n) != 1)
break;
so_far += n;
}
return 0;
}
Compile with: gcc -Wall -g -O0 main.c
Running this code with `N = 50000` and using `sscanf`, I get:
$ time ./a.out
real 0m1.602s
user 0m1.596s
sys 0m0.000s
Running it with `N = 50000` and using the substitute code, I get:
$ time ./a.out
real 0m0.002s
user 0m0.000s
sys 0m0.000s
Which is considrably smaller. Note that this shows that the part with
`malloc` and initialization take very small time. Running callgrind
shows that almost all of the time when using `sscanf` is spent in
`rawmemchr`. Indeed, using gdb and randomly hitting CTRL+C, you always
end up with a stack trace like this:
#0 __rawmemchr_ia32 () at ../sysdeps/i386/i686/multiarch/../../rawmemchr.S:167
#1 0xb7e78a06 in _IO_str_init_static_internal () at strops.c:44
#2 0xb7e5c857 in __GI___isoc99_vsscanf () at isoc99_vsscanf.c:41
#3 0xb7e5c7cf in __isoc99_sscanf () at isoc99_sscanf.c:31
#4 0x08048494 in _debug_helper () at main.c:11
#5 0x08048517 in main () at main.c:41
This means that `rawmemchr` is slowing down `sscanf` by an unnecessary
degree. To further prove this point (and to confirm my guess that
`rawmemchr` is reading the whole string), here are a couple more tests:
With `N = 25000` and using `sscanf`:
$ time ./a.out
real 0m0.407s
user 0m0.404s
sys 0m0.000s
With `N = 12500` and using `sscanf`:
$ time ./a.out
real 0m0.106s
user 0m0.104s
sys 0m0.000s
- This clearly shows an O(N^2) behavior. The main loop of the program is
+ This clearly shows an `O(N^2)` behavior. The main loop of the program is
`O(N)`, which means `sscanf` is running at `O(N)`. For large `N`, this
is significant. On the other hand, the actual behavior of `sscanf`
should be to read from the string according to the format string and no
more, which in this case (using `%d` and "123" as values) is of constant
time.
---
Note: I'm on Ubuntu 14.04, using gcc 4.8.2. Most importantly, this is
glibc 2.19.
** Description changed:
This is a bug on glib, and I'm not sure if it applies upstream too
- (although I suspect it does).
+ (although I suspect it does). Upstream bug link:
+
+ https://sourceware.org/bugzilla/show_bug.cgi?id=17577
The bug is that `sscanf` (`vsscanf` actually) is very slow on large
strings. It _seems_ like `vsscanf` is first trying to find the end of
the string (with `rawmemchr`), which is expensive given that most of the
string is not going to be read.
Here's a test code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#define N 50000
static int _debug_helper(const char *src, int *a, int *n)
{
#if 1
return sscanf(src, "%d%n", a, n);
#else
char *end;
errno = 0;
*a = strtol(src, &end, 10);
*n = end - src;
return errno == 0 && *n > 0;
#endif
}
int main()
{
int i;
int a;
int n;
int so_far = 0;
char *big_string = malloc(N * 4 + 1);
for (i = 0; i < N; ++i)
strcpy(big_string + i * 4, "123 ");
big_string[N * 4] = '\0';
while (1)
{
if (_debug_helper(big_string + so_far, &a, &n) != 1)
break;
so_far += n;
}
return 0;
}
Compile with: gcc -Wall -g -O0 main.c
Running this code with `N = 50000` and using `sscanf`, I get:
$ time ./a.out
real 0m1.602s
user 0m1.596s
sys 0m0.000s
Running it with `N = 50000` and using the substitute code, I get:
$ time ./a.out
real 0m0.002s
user 0m0.000s
sys 0m0.000s
Which is considrably smaller. Note that this shows that the part with
`malloc` and initialization take very small time. Running callgrind
shows that almost all of the time when using `sscanf` is spent in
`rawmemchr`. Indeed, using gdb and randomly hitting CTRL+C, you always
end up with a stack trace like this:
#0 __rawmemchr_ia32 () at ../sysdeps/i386/i686/multiarch/../../rawmemchr.S:167
#1 0xb7e78a06 in _IO_str_init_static_internal () at strops.c:44
#2 0xb7e5c857 in __GI___isoc99_vsscanf () at isoc99_vsscanf.c:41
#3 0xb7e5c7cf in __isoc99_sscanf () at isoc99_sscanf.c:31
#4 0x08048494 in _debug_helper () at main.c:11
#5 0x08048517 in main () at main.c:41
This means that `rawmemchr` is slowing down `sscanf` by an unnecessary
degree. To further prove this point (and to confirm my guess that
`rawmemchr` is reading the whole string), here are a couple more tests:
With `N = 25000` and using `sscanf`:
$ time ./a.out
real 0m0.407s
user 0m0.404s
sys 0m0.000s
With `N = 12500` and using `sscanf`:
$ time ./a.out
real 0m0.106s
user 0m0.104s
sys 0m0.000s
This clearly shows an `O(N^2)` behavior. The main loop of the program is
`O(N)`, which means `sscanf` is running at `O(N)`. For large `N`, this
is significant. On the other hand, the actual behavior of `sscanf`
should be to read from the string according to the format string and no
more, which in this case (using `%d` and "123" as values) is of constant
time.
---
Note: I'm on Ubuntu 14.04, using gcc 4.8.2. Most importantly, this is
glibc 2.19.
--
You received this bug notification because you are a member of Ubuntu
Foundations Bugs, which is subscribed to glibc in Ubuntu.
https://bugs.launchpad.net/bugs/1391510
Title:
sscanf extremely slow on large strings
Status in “glibc” package in Ubuntu:
New
Bug description:
This is a bug on glib, and I'm not sure if it applies upstream too
(although I suspect it does). Upstream bug link:
https://sourceware.org/bugzilla/show_bug.cgi?id=17577
The bug is that `sscanf` (`vsscanf` actually) is very slow on large
strings. It _seems_ like `vsscanf` is first trying to find the end of
the string (with `rawmemchr`), which is expensive given that most of
the string is not going to be read.
Here's a test code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#define N 50000
static int _debug_helper(const char *src, int *a, int *n)
{
#if 1
return sscanf(src, "%d%n", a, n);
#else
char *end;
errno = 0;
*a = strtol(src, &end, 10);
*n = end - src;
return errno == 0 && *n > 0;
#endif
}
int main()
{
int i;
int a;
int n;
int so_far = 0;
char *big_string = malloc(N * 4 + 1);
for (i = 0; i < N; ++i)
strcpy(big_string + i * 4, "123 ");
big_string[N * 4] = '\0';
while (1)
{
if (_debug_helper(big_string + so_far, &a, &n) != 1)
break;
so_far += n;
}
return 0;
}
Compile with: gcc -Wall -g -O0 main.c
Running this code with `N = 50000` and using `sscanf`, I get:
$ time ./a.out
real 0m1.602s
user 0m1.596s
sys 0m0.000s
Running it with `N = 50000` and using the substitute code, I get:
$ time ./a.out
real 0m0.002s
user 0m0.000s
sys 0m0.000s
Which is considrably smaller. Note that this shows that the part with
`malloc` and initialization take very small time. Running callgrind
shows that almost all of the time when using `sscanf` is spent in
`rawmemchr`. Indeed, using gdb and randomly hitting CTRL+C, you always
end up with a stack trace like this:
#0 __rawmemchr_ia32 () at ../sysdeps/i386/i686/multiarch/../../rawmemchr.S:167
#1 0xb7e78a06 in _IO_str_init_static_internal () at strops.c:44
#2 0xb7e5c857 in __GI___isoc99_vsscanf () at isoc99_vsscanf.c:41
#3 0xb7e5c7cf in __isoc99_sscanf () at isoc99_sscanf.c:31
#4 0x08048494 in _debug_helper () at main.c:11
#5 0x08048517 in main () at main.c:41
This means that `rawmemchr` is slowing down `sscanf` by an unnecessary
degree. To further prove this point (and to confirm my guess that
`rawmemchr` is reading the whole string), here are a couple more
tests:
With `N = 25000` and using `sscanf`:
$ time ./a.out
real 0m0.407s
user 0m0.404s
sys 0m0.000s
With `N = 12500` and using `sscanf`:
$ time ./a.out
real 0m0.106s
user 0m0.104s
sys 0m0.000s
This clearly shows an `O(N^2)` behavior. The main loop of the program
is `O(N)`, which means `sscanf` is running at `O(N)`. For large `N`,
this is significant. On the other hand, the actual behavior of
`sscanf` should be to read from the string according to the format
string and no more, which in this case (using `%d` and "123" as
values) is of constant time.
---
Note: I'm on Ubuntu 14.04, using gcc 4.8.2. Most importantly, this is
glibc 2.19.
To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/glibc/+bug/1391510/+subscriptions
More information about the foundations-bugs
mailing list