Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The new implementation is a bit slower than the old #73

Closed
tarkatronic opened this issue Sep 11, 2020 · 20 comments
Closed

The new implementation is a bit slower than the old #73

tarkatronic opened this issue Sep 11, 2020 · 20 comments
Labels
bug Something isn't working Hacktoberfest help wanted Extra attention is needed
Milestone

Comments

@tarkatronic
Copy link
Contributor

🐛 Bug Report

When self-scanning tartufo, I found that it was taking a bit longer (using time tartufo to measure) to run a full scan. In general, it changed from about 13 seconds to about 20 seconds. I did some preliminary testing and wasn't able to find any obvious reasons why. It'd be good to go through at some point with a fine-toothed profiling comb and see what can be optimized in here.

On a fun note, I found that the @lru_cache decorators I used did shave about a second off the run time! Yay!

It's worth mentioning that I built the new implementation with the intention of optimizing for memory rather than speed, using things such as generators etc. I don't know if some of this ended up being detrimental to the speed. But may be worth investigating.

To Reproduce

Run the following commands and you'll be able to see the discrepancy.

git checkout v1.x
time tartufo
git checkout master
time tartufo

Expected Behavior

The new version should be a similar speed, if not faster. Certainly not nearly twice as slow.

@tarkatronic tarkatronic added bug Something isn't working help wanted Extra attention is needed labels Sep 11, 2020
@bsrz
Copy link

bsrz commented Oct 9, 2020

Seeing the same as described.

Using 1.1.2

$ time tartufo --exclude-paths .tartufo/exclude_paths.txt .
Results have been saved in /var/folders/w5/dkpm7qm95s3gbtffn66fwxb00000gn/T/tmp7sodkfkj
tartufo --exclude-paths .tartufo/exclude_paths.txt .  16.10s user 11.53s system 97% cpu 28.389 total

Using 2.0.0

$ time tartufo scan-local-repo .
tartufo scan-local-repo .  266.86s user 206.12s system 99% cpu 7:55.45 total

@tarkatronic
Copy link
Contributor Author

@bsarrazin I hate to ask you to spend more time running these, but do you know if those timings are consistent, with minimal potential outside influences? (Other heavy processes running on the system)

@rscottbailey
Copy link
Contributor

I tried this against the tartufo repo itself on my (physical) Debian system, just for grins. Basically the same behavior, and the 2.0.1 times look pretty stable:

1.1.2:

$ time tartufo

[...spewage clipped...]
real    0m8.190s
user    0m4.614s
sys     0m1.756s

2.0.1:

$ time tartufo scan-local-repo .

real    0m14.562s
user    0m7.017s
sys     0m4.012s
$ time tartufo scan-local-repo .

real    0m15.262s
user    0m7.182s
sys     0m3.919s
$ time tartufo scan-local-repo .

real    0m15.074s
user    0m7.151s
sys     0m3.983s

@tarkatronic
Copy link
Contributor Author

Well I'm glad to see that you're experiencing similar deltas to what I was, @rscottbailey. But I'm very concerned by the deltas that @bsarrazin is seeing. I would love to try to isolate what exactly is causing this, and why it would be so much worse on a different repo.

I haven't had much experience myself with Python profiling tools, but I will try to take some time to dig in. I know cProfile is the standard, and I also was recently pointed at https://github.com/benfred/py-spy which seems like it might be super cool and helpful.

@rscottbailey
Copy link
Contributor

Hmm. I don't know if he's looking at a different (larger) repository or running on a dog-slow system, but i'm still seeing pretty much the 2x elapsed/user/system bloat that was originally reported. I don't know if this is valuable or not (clipped after the first few dozen lines):

$ python -m cProfile -s time /usr/local/src/gd/venv-tartufo/lib/python3.8/site-packages/tartufo/__main__.py scan-local-repo .
         2439524 function calls (2426549 primitive calls) in 15.617 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    11597    4.242    0.000    4.242    0.000 {method 'acquire' of '_thread.lock' objects}
     1174    1.746    0.001    1.746    0.001 {built-in method posix.read}
   273902    1.294    0.000    1.294    0.000 util.py:146(get_strings_of_set)
     1168    0.987    0.001    0.987    0.001 {built-in method _posixsubprocess.fork_exec}
     7757    0.470    0.000    0.470    0.000 {method 'readline' of '_io.BufferedReader' objects}
    15372    0.459    0.000    0.459    0.000 {method 'findall' of 're.Pattern' objects}
     6532    0.339    0.000    0.340    0.000 {method 'flush' of '_io.BufferedWriter' objects}
      854    0.289    0.000    1.644    0.002 scanner.py:267(scan_entropy)
     2059    0.288    0.000    0.288    0.000 {built-in method builtins.dir}
     1168    0.286    0.000    3.725    0.003 subprocess.py:1550(_execute_child)
     8505    0.237    0.000    0.252    0.000 {built-in method io.open}
     1159    0.210    0.000    7.757    0.007 diff.py:411(_index_from_patch_format)
     2056    0.162    0.000    0.610    0.000 configparser.py:1317(__init__)
   243990    0.160    0.000    0.160    0.000 {method 'match' of 're.Pattern' objects}
     1168    0.113    0.000    0.320    0.000 subprocess.py:1097(_close_pipe_fds)
     2319    0.107    0.000    0.107    0.000 {built-in method _thread.start_new_thread}
     2338    0.096    0.000    0.124    0.000 contextlib.py:429(callback)
    68862    0.095    0.000    0.155    0.000 os.py:800(fsencode)
     4106    0.088    0.000    0.401    0.000 symbolic.py:137(_get_ref_info_helper)
     6532    0.087    0.000    0.999    0.000 cmd.py:1054(__get_object_header)

@tarkatronic
Copy link
Contributor Author

tarkatronic commented Oct 9, 2020

One thing I'm seeing here and from my own profiling is that a lot of this appears to be in the git and diff code. I can't help but wondering, is this a side effect of updating the underlying libraries?

1.x has:
https://github.com/godaddy/tartufo/blob/v1.x/poetry.lock#L269-L278

[[package]]
category = "main"
description = "Python Git Library"
name = "gitpython"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.1.15"

[package.dependencies]
gitdb2 = ">=2,<3"

While 2.x has:
https://github.com/godaddy/tartufo/blob/master/poetry.lock#L192-L201

[[package]]
name = "gitpython"
version = "3.1.9"
description = "Python Git Library"
category = "main"
optional = false
python-versions = ">=3.4"

[package.dependencies]
gitdb = ">=4.0.1,<5"

Is it possible that the performance changes come from those libraries? (He says, hopefully)

If so, perhaps it's time to look into #66.

@rscottbailey
Copy link
Contributor

Well, I built fresh venvs for both versions just now - sitting on top of Linux with python 3.8.6, so I have no library skew (pip reports the same versions of everything except tartufo itself) and I'm not worrying about Windows. For rough comparison above, here's a 1.1.2 profile:

         1946575 function calls (1939335 primitive calls) in 10.153 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     4861    1.697    0.000    1.697    0.000 {method 'acquire' of '_thread.lock' objects}
   382238    1.637    0.000    1.638    0.000 scanner.py:65(get_strings_of_set)
      506    0.883    0.002    0.883    0.002 {built-in method posix.read}
    18576    0.649    0.000    0.649    0.000 {method 'findall' of 're.Pattern' objects}
      495    0.476    0.001    0.476    0.001 {built-in method _posixsubprocess.fork_exec}
     4754    0.436    0.000    0.436    0.000 {method 'readline' of '_io.BufferedReader' objects}
     1032    0.340    0.000    2.493    0.002 scanner.py:143(find_entropy)
     3491    0.255    0.000    0.463    0.000 scanner.py:50(shannon_entropy)
     1476    0.211    0.000    0.211    0.000 {built-in method builtins.print}
     3370    0.208    0.000    0.208    0.000 {method 'flush' of '_io.BufferedWriter' objects}
      992    0.136    0.000    0.136    0.000 {built-in method builtins.dir}
      495    0.130    0.000    1.836    0.004 subprocess.py:1550(_execute_child)
     4113    0.129    0.000    0.139    0.000 {built-in method io.open}
   178239    0.120    0.000    0.120    0.000 {method 'count' of 'str' objects}
      485    0.099    0.000    3.845    0.008 diff.py:411(_index_from_patch_format)
   116861    0.074    0.000    0.074    0.000 {method 'match' of 're.Pattern' objects}
      989    0.067    0.000    0.278    0.000 configparser.py:1317(__init__)
      495    0.064    0.000    0.155    0.000 subprocess.py:1097(_close_pipe_fds)
     1410    0.062    0.000    0.176    0.000 commit.py:445(_deserialize)
    29155    0.061    0.000    0.087    0.000 os.py:800(fsencode)
210362/209805    0.057    0.000    0.058    0.000 {built-in method builtins.len}
    50950    0.055    0.000    0.055    0.000 {method 'split' of 'str' objects}
      971    0.045    0.000    0.045    0.000 {built-in method _thread.start_new_thread}
     1960    0.044    0.000    0.191    0.000 symbolic.py:137(_get_ref_info_helper)
    56768    0.043    0.000    0.043    0.000 {built-in method math.log}

@rscottbailey
Copy link
Contributor

One thing that strikes me is that v2 is doing ~2.4M primitive calls vs v1's ~1.9M calls, even though v1 is doing much more work to actually spew all of the findings to stdout

@bsrz
Copy link

bsrz commented Oct 12, 2020

I am running this on an almost brand new MacBook Pro 16", fresh installation of macOS from 3 weeks ago, running using pyenv in python 3.8.2.

2020-10-12 at 8 33 AM

We're scanning the iOS repo for GoDaddy Conversations, ran it again this morning after a reboot, not much running except Mail, Safari, iTerm2, Slack, watching an MxPx show:

tartufo 1.1.2

$ time tartufo --exclude-paths .tartufo/exclude_paths.txt .
15.42s user
11.90s system
97% cpu 27.949 total

tartufo 2.0.1

$ time tartufo --regex --entropy --exclude-paths .tartufo/exclude_paths.txt scan-local-repo .
242.55s user
184.42s system
99% cpu 7:10.09 total

@rscottbailey
Copy link
Contributor

One thought that crossed my mind here -- can you retry the 1.1.2 run, adding --regex? That wasn't enabled by default in that version (and frankly I've forgotten if it's the default for 2.x). I can't believe that would be so horrible as to account for all of the increase, but that at least would get us in the apples-to-apples ballpark.

@bsrz
Copy link

bsrz commented Oct 12, 2020

@rscottbailey just tried with regex

time tartufo --regex --exclude-paths .tartufo/exclude_paths.txt .
17.53s user
12.63s system
97% cpu 30.793 total

@rscottbailey
Copy link
Contributor

rscottbailey commented Oct 12, 2020

@bsarrazin Can you share your repo URL? (Assuming it's on GHE) Hoping to poke at tartufo a bit more without deluging you with a bunch of "what about this?" variant requests... Send to me on slack (rbailey) if that's easier.

@bsrz
Copy link

bsrz commented Oct 12, 2020

URL sent by Slack

@tarkatronic
Copy link
Contributor Author

Doing a little bit of shotgun debugging here and checking dependencies, there doesn't seem to be any big difference:

> poetry add "GitPython<3.0.0"
  • Removing gitdb (4.0.5)
  • Installing smmap2 (3.0.1)
  • Installing gitdb2 (2.0.6)
  • Updating gitpython (3.1.9 -> 2.1.15)
> time tartufo scan-local-repo . && time tartufo scan-local-repo . && time tartufo scan-local-repo . && time tartufo scan-local-repo . && time tartufo scan-local-repo .
tartufo scan-local-repo .  6.73s user 13.35s system 30% cpu 1:06.74 total
tartufo scan-local-repo .  6.72s user 13.69s system 24% cpu 1:21.76 total
tartufo scan-local-repo .  6.87s user 13.76s system 25% cpu 1:19.96 total
tartufo scan-local-repo .  6.88s user 13.85s system 25% cpu 1:21.10 total
tartufo scan-local-repo .  6.86s user 13.76s system 25% cpu 1:22.43 total
> poetry add "GitPython@latest"
  • Removing gitdb2 (2.0.6)
  • Removing smmap2 (3.0.1)
  • Installing gitdb (4.0.5)
  • Updating gitpython (2.1.15 -> 3.1.9)
> time tartufo scan-local-repo . && time tartufo scan-local-repo . && time tartufo scan-local-repo . && time tartufo scan-local-repo . && time tartufo scan-local-repo .
tartufo scan-local-repo .  7.84s user 16.72s system 29% cpu 1:22.54 total
tartufo scan-local-repo .  7.92s user 16.76s system 28% cpu 1:27.02 total
tartufo scan-local-repo .  7.93s user 16.78s system 28% cpu 1:25.29 total
tartufo scan-local-repo .  7.89s user 16.74s system 28% cpu 1:25.91 total
tartufo scan-local-repo .  7.90s user 16.77s system 29% cpu 1:23.60 total

Vs a baseline with v1.1.2

> time tartufo --repo-path .
tartufo --repo-path .  4.57s user 7.54s system 55% cpu 21.670 total

This is a pretty significant increase I'm seeing against this project's own codebase now.

One other thing I'm curious about is, could this be related to the signature-based exclusions? We are now calculating a hash based on every match found and comparing that against a list of exclusions. While this is using BLAKE2, which should be quite fast, it is definitely a >0 cost.

@tarkatronic
Copy link
Contributor Author

I commented out the calls to util.generate_signature to see how much of an impact those have, and it it had... mostly a negligible difference! So that's exciting; BLAKE2 seems to be living up to its promise of speed:

> time tartufo scan-local-repo .
# Repeated by hand because issues pop up when signature checking is missing
tartufo scan-local-repo .  7.49s user 15.46s system 34% cpu 1:05.89 total
tartufo scan-local-repo .  7.78s user 16.51s system 29% cpu 1:22.78 total
tartufo scan-local-repo .  7.78s user 16.43s system 30% cpu 1:20.52 total
tartufo scan-local-repo .  7.81s user 16.41s system 31% cpu 1:18.10 total
tartufo scan-local-repo .  7.76s user 16.09s system 29% cpu 1:19.81 total

@jgowdy
Copy link
Contributor

jgowdy commented Oct 21, 2020

I believe this is what's wrong: #119

@jgowdy
Copy link
Contributor

jgowdy commented Oct 23, 2020

Using strace on tartufo, filtered on "^open"

openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/refs/heads/amanchanda/SML-10777", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.gitmodules", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/HEAD", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/refs/heads/amanchanda/SML-10777", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/HEAD", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/refs/heads/amanchanda/SML-10777", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.gitmodules", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/HEAD", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/refs/heads/amanchanda/SML-10777", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/HEAD", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/refs/heads/amanchanda/SML-10777", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.gitmodules", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/HEAD", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/refs/heads/amanchanda/SML-10777", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/HEAD", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/refs/heads/amanchanda/SML-10777", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.gitmodules", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/HEAD", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/refs/heads/amanchanda/SML-10777", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/HEAD", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/refs/heads/amanchanda/SML-10777", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.gitmodules", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/HEAD", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/refs/heads/amanchanda/SML-10777", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/HEAD", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/refs/heads/amanchanda/SML-10777", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.gitmodules", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/HEAD", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/refs/heads/amanchanda/SML-10777", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/HEAD", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.git/refs/heads/amanchanda/SML-10777", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/mnt/c/Users/jgowdy/projects/other-godaddy/Leka-iOS/.gitmodules", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)

Shows tartufo (gitpython?) absolutely hammering the exact same files over and over again. In my testing with @djlarsu we found that the number of syscalls that tartufo does in 2.x is massively higher than the number in 1.x. I wonder if we aren't accidentally recreating / reloading the references in the scan loop somehow.

@jgowdy
Copy link
Contributor

jgowdy commented Oct 23, 2020

Okay so here's the key data:

My PR version that doesn't go remote when you say local
strace tartufo scan-local-repo ../../other-godaddy/Leka-iOS 2>&1 | grep ^open | wc -l
126655

Tartufo v2.0.1
jgowdy@private:/mnt/c/Users/jgowdy/projects/opensource/tartufo$ strace tartufo scan-local-repo ../../other-godaddy/Leka-iOS/ 2>&1 | grep -e "^open" | wc -l
1020848

Tartufo v1.1.2
strace tartufo --repo-path ../../other-godaddy/Leka-iOS/ 2>&1 | grep -e "^open" | wc -l
65080

Tartufo v1.1.2 did 65,000 file opens to examine this repo.
Tartufo v2.0.1 did 1,000,000 file opens to examine this repo.
Tartufo 2.x with my PR #119 did 126,000 file opens to examine this repo.

@jgowdy
Copy link
Contributor

jgowdy commented Oct 23, 2020

There are 1,532 files in this repo, and the size of the repo is 37MB. There are 1,308 revisions / commits on all branches.

find . | wc -l
1532

du -h -d 1
21M     ./.git
4.0K    ./.github
12K     ./.images
0       ./.tartufo
196K    ./DomainKit
16K     ./fastlane
7.1M    ./Leka
488K    ./Leka.xcodeproj
0       ./Leka.xcworkspace
48K     ./LekaTests
252K    ./LekaUITests
264K    ./TestKit
37M     .

git rev-list --all | wc -l
1308

git rev-list --remotes --all | wc -l
1308

https://github.com/gdcorp-mobile/Leka-iOS

@mxhenry-godaddy mxhenry-godaddy added this to the Version 2.1 milestone Nov 3, 2020
@tarkatronic tarkatronic modified the milestones: Version 2.1, Version 3.0 Nov 30, 2020
@tarkatronic
Copy link
Contributor Author

Fixed in #233

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working Hacktoberfest help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

5 participants