Skip to content

jq startup has gotten slow due to growth in builtins and linking inefficiencies #1411

@h2suzuki

Description

@h2suzuki

Thank you for this great command jq!

Recently, I have compiled jq-1.5 (master branch HEAD) on Raspberry Pi3, and found significant slow down since jq-1.4.

Here is the information I found:

jq-1.4

Note that ./configure is done without any options. Within the build directory, I did the following commands.

# ./jq --version jq-1.4 # time echo '{}' | strace -wc ./jq . {} % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 17.64 0.000960 69 14 mmap2 16.66 0.000907 907 1 execve 12.36 0.000673 61 11 read 10.73 0.000584 65 9 mprotect 8.49 0.000462 77 6 1 open 6.14 0.000334 48 7 fstat64 6.01 0.000327 109 3 munmap 4.80 0.000261 44 6 close 4.74 0.000258 65 4 3 access 4.65 0.000253 42 6 lseek 2.70 0.000147 49 3 brk 1.47 0.000080 80 1 write 1.10 0.000060 60 1 ioctl 0.94 0.000051 51 1 cacheflush 0.83 0.000045 45 1 uname 0.75 0.000041 41 1 set_tls ------ ----------- ----------- --------- --------- ---------------- 100.00 0.005443 75 4 total real 0m0.038s user 0m0.010s sys 0m0.010s 

jq-1.5 HEAD

For 1.5, ./configure --without-oniguruma is done.

# ./jq --version jq-1.5rc2-237-gcb3d5af # time echo '{}' | strace -wc ./jq . {} % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 15.36 0.000950 68 14 mmap2 14.82 0.000917 102 9 brk 14.57 0.000901 901 1 execve 9.75 0.000603 55 11 read 9.41 0.000582 65 9 mprotect 7.44 0.000460 77 6 1 open 5.38 0.000333 48 7 fstat64 5.04 0.000312 104 3 munmap 4.30 0.000266 44 6 close 4.28 0.000265 66 4 3 access 4.17 0.000258 43 6 lseek 1.39 0.000086 86 1 write 0.97 0.000060 60 1 ioctl 0.86 0.000053 53 1 getcwd 0.84 0.000052 52 1 cacheflush 0.79 0.000049 49 1 uname 0.63 0.000039 39 1 set_tls ------ ----------- ----------- --------- --------- ---------------- 100.00 0.006186 82 4 total real 0m0.895s user 0m0.830s sys 0m0.010s 

You can see the elapsed time becomes almost 1 second for this minimum JSON and 23.5 times longer than 1.4.

Here is another trace of jq-1.5.

# time echo '{}' | strace -r ./jq . 0.000000 execve("./jq", ["./jq", "."], [/* 47 vars */]) = 0 0.001837 brk(0) = 0x12f5000 0.000298 uname({sys="Linux", node="raspberry", ...}) = 0 0.000201 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) 0.000242 mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76fa1000 0.000226 access("/etc/ld.so.preload", R_OK) = 0 0.000201 open("/etc/ld.so.preload", O_RDONLY|O_CLOEXEC) = 3 0.000222 fstat64(3, {st_mode=S_IFREG|0644, st_size=42, ...}) = 0 0.000219 mmap2(NULL, 42, PROT_READ|PROT_WRITE, MAP_PRIVATE, 3, 0) = 0x76fa0000 0.000214 close(3) = 0 0.000240 open("/usr/lib/arm-linux-gnueabihf/libarmmem.so", O_RDONLY|O_CLOEXEC) = 3 ... (omitted) ... 0.000451 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f9f000 0.000328 set_tls(0x76f9f4c0, 0x76f9fba8, 0x76fa4050, 0x76f9f4c0, 0x76fa4050) = 0 0.000473 mprotect(0x76ec0000, 8192, PROT_READ) = 0 0.000299 mprotect(0x76f3f000, 4096, PROT_READ) = 0 0.000240 mprotect(0x76f5f000, 20480, PROT_READ|PROT_WRITE) = 0 0.000300 mprotect(0x76f5f000, 20480, PROT_READ|PROT_EXEC) = 0 0.000257 cacheflush(0x76f5f000, 0x76f64000, 0, 0x15, 0x7ea53dd0) = 0 0.000313 mprotect(0x76fa3000, 4096, PROT_READ) = 0 0.000226 munmap(0x76f41000, 120366) = 0 0.001042 brk(0) = 0x12f5000 0.000234 brk(0x1316000) = 0x1316000 0.000392 ioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B9600 opost isig icanon echo ...}) = 0 0.000752 getcwd("/root/Downloads/jq", 4096) = 19 0.000663 open("/root/.jq", O_RDONLY) = -1 ENOENT (No such file or directory) 0.032603 brk(0x1337000) = 0x1337000 0.018625 brk(0x1358000) = 0x1358000 0.041019 brk(0x1379000) = 0x1379000 0.808403 brk(0x139a000) = 0x139a000 0.389545 brk(0x1399000) = 0x1399000 0.001031 brk(0x131e000) = 0x131e000 0.000708 fstat64(0, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0 0.000251 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f9e000 0.000291 read(0, "{}\n", 4096) = 3 0.000443 fstat64(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 0), ...}) = 0 0.000235 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f9d000 0.000268 write(1, "\33[1;39m{}\33[0m\n", 14{} ) = 14 0.000272 read(0, "", 4096) = 0 0.000215 close(1) = 0 0.000164 munmap(0x76f9d000, 4096) = 0 0.000430 exit_group(0) = ? 0.000826 +++ exited with 0 +++ real 0m1.445s user 0m0.990s sys 0m0.020s 

The major difference comes from brk() system calls after open("/root/.jq", O_RDONLY).

 0.032603 brk(0x1337000) = 0x1337000 0.018625 brk(0x1358000) = 0x1358000 0.041019 brk(0x1379000) = 0x1379000 0.808403 brk(0x139a000) = 0x139a000 0.389545 brk(0x1399000) = 0x1399000 0.001031 brk(0x131e000) = 0x131e000 

0.808403 and 0.389545 seconds are not taken by brk() itself as indicated by strace -wc
These are implying something heavy going on in the user space between these system calls.

jq-1.4 does not have these brk() calls at all. The problematic part is introduced by jq-1.5.
I put strace output of jq-1.4 below after open("/root/.jq", O_RDONLY).

... (omitted) ... 0.000165 munmap(0x76ead000, 120366) = 0 0.000882 brk(0) = 0x1213000 0.000163 brk(0x1234000) = 0x1234000 0.000590 open("/root/.jq", O_RDONLY) = -1 ENOENT (No such file or directory) 0.008392 fstat64(0, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0 0.000301 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f0a000 0.000215 read(0, "{}\n", 4096) = 3 0.000333 ioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B9600 opost isig icanon echo ...}) = 0 0.000217 fstat64(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 0), ...}) = 0 0.000280 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f09000 0.000241 write(1, "\33[37m{}\33[0m\n", 12{} ) = 12 0.000217 read(0, "", 4096) = 0 0.000171 close(0) = 0 0.000135 munmap(0x76f0a000, 4096) = 0 0.000497 exit_group(0) = ? 0.000587 +++ exited with 0 +++ real 0m0.043s user 0m0.020s sys 0m0.010s 

Just to make it clear, we have much free memory, so the issue is not triggered by memory shortage.

# free -h total used free shared buffers cached Mem: 733M 616M 116M 55M 86M 340M -/+ buffers/cache: 189M 543M Swap: 370M 36M 334M 

Thank you in advance!

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions