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

Parallel downloads #825

Open
cool-RR opened this issue Mar 4, 2013 · 51 comments
Open

Parallel downloads #825

cool-RR opened this issue Mar 4, 2013 · 51 comments
Labels
resolution: deferred till PR Further discussion will happen when a PR is made type: enhancement Improvements to functionality

Comments

@cool-RR
Copy link

cool-RR commented Mar 4, 2013

How about having pip download all the packages in parallel instead of waiting for each one to finish before downloading the next?

After that is implemented, how about having pip start installing one package while it's downloading the next ones?

@cool-RR

This comment has been minimized.

@dstufft
Copy link
Member

dstufft commented Jan 20, 2014

Yes we do care. However it's largely a problem of there being bigger issues to tackle first. Sorry that nobody responded to your ticket though!

@cool-RR
Copy link
Author

cool-RR commented Jan 20, 2014

If we're looking at parallelizing just the download part, is it much more
complex than sticking a concurrent.futures.ThreadPoolExecutor on it and
adding a configuration option to turn it off?

On Mon, Jan 20, 2014 at 7:23 PM, Donald Stufft notifications@github.comwrote:

Yes we do care. However it's largely a problem of there being bigger
issues to tackle first. Sorry that nobody responded to your ticket though!


Reply to this email directly or view it on GitHubhttps://github.com//issues/825#issuecomment-32779319
.

@jquast
Copy link

jquast commented Jun 23, 2014

I disagree. Most downloads are far slower than a disk can write, even if writing many files. As an optional --feature, I think it is a trival choice -- people who know they are downloading over a LAN and onto a slow disk would chose not to --enable such feature. People like myself downloading several-hundred megabyte packages over broadband would gain much to chose to use such --feature.

@sholsapp
Copy link

+1 to parallel download (and install if possible).

@mattvonrocketstein
Copy link

+1, I frequently work on projects that have hundreds of dependencies and are built from scratch repeatedly throughout the average workday. This stuff crawls even with a pypi mirror on the LAN so having this would be great! http://stackoverflow.com/questions/11021130/parallel-pip-install Options presented are not great, but it does show there is (and has been) interest in a solution to the issue

@xavfernandez xavfernandez added the type: enhancement Improvements to functionality label Oct 7, 2015
@jamesob
Copy link

jamesob commented Oct 20, 2015

+1, this would be huge. Willing to take a crack at implementing.

@mattvonrocketstein
Copy link

If anyone is curious why this should be a core feature and not something an external tool provides I can summarize the problem. Using something like "xargs --max-args=1 --max-procs=4 sudo pip install < requires.txt" in general can actually increase the total downloads quite a bit. This happens when for example "django-foo" and "django-bar" both require Django, but specify different constraints for which version is acceptable. I think pip itself has to compute the unified requirement set (probably including requirements-of-requirements) to avoid redundant downloads.

@sholsapp
Copy link

Do knowledgeable people know if parallel install is an option? When I tried implementing a build system that executed pip in parallel, I hit problems specifically related to installing modules that had native extensions, but didn't dig further into it. If people know what might be wrong there, I'd love to know.

@fillest
Copy link

fillest commented Apr 27, 2016

+1 to parallelization. Not just for downloading but also for things like "Collecting ... File was already downloaded" with pip wheel. It is painfully slow for projects with huge dependency list and multiple hosts, it sometimes becomes one of deployment bottlenecks. I had to add a cache layer on our project - run it only if requirements.txt has changed.

@RonnyPfannschmidt
Copy link
Contributor

note that enabling complete "parallelism" amounts to a relatively complete re-factoring of many internals
this needs an idea for a starting point and quite many re-factoring steps over time

also there is ux issues, while downloading/unpacking is simple, wheel generation and setup.py invocation are full of error potentials/conditions (however they are needed until everybody uses wheels whenever possible and they are needed right in the middle of execution)

@mattvonrocketstein
Copy link

so if the UX complication is sidestepped with only parallel downloads/unpacking, doesn't it make sense for that to be the first goal? full parallelism does seem like a huge undertaking.. i'm no expert but as far as that implementation here's two ideas that came to mind

a) in the set of all the requirements and the requirements of requirements, subsets must be discovered wherein individual requirements must be installed sequentially. all such subsets can then be installed in parallel at least. as far as ux, errors here can be shown whenever they occur because any error encountered is truly an error.

b) in the set of all the requirements and the requirements of requirements, a parallel worker pops something out and tries installing it. if the req installs successfully, great. if it fails, we assume another requirement must be installed first and put it back in. as far as ux, errors here must be suppressed unless they persist until the end of this procedure, because any error encountered might be something we can recover from later in the process.

@RonnyPfannschmidt
Copy link
Contributor

the ux issue cannot be side-stepped if any of the downloaded packages is a sdist

the requirement graph is nonlinear and changes whenever more about the dependencies and constrains of a just freshly downloaded package gets known

as soon as a sdist is downloaded, the build/egg-info/wheel process of it has to be triggered

@pradyunsg
Copy link
Member

pradyunsg commented May 16, 2017

Here's what I think...

To be able to have parallel downloads, pip would need to determine which packages are to be installed and where they need to be fetched from. As of today, it is not possible since pip cannot determine the dependencies of a package without downloading it. While there's a bunch of interesting p

That said, this is definitely something that'll be pretty awesome to have. :)

@fruechel
Copy link

I understand that you won't know follow-up dependencies before downloading a package. But looking at a requirements.txt, I would assume you could start a parallel download of all of those packages and then expand the list of things to download as you discover more dependencies. The only edge case I can think of right now would be different version requirements on the same dependency. But that problem exists with regular downloading as well I would assume.

@pradyunsg
Copy link
Member

pradyunsg commented May 17, 2017

I didn't mean to post that message yet. Oops.

Anyway, the point I was making was that the way pip currently handles dependencies, there's a race condition where 2 packages depend on a common third with compatible but different version specifiers. Whichever package is downloaded first, its specifier would be used and a bad thing - you have behaviour that changes because of how the network behaved.

The only right way to do this is then to have dependencies metadata on pypi and only then can we determine the packages beforehand and proceed to parallel download/installation. Or somehow managing this during downloading?

Or I'm missing something about this issue.

@pradyunsg
Copy link
Member

@fruechel Yes. Except with the serial downloads, the version of the common dependency is deterministic.

@fruechel
Copy link

Understood. You're right, it would make the process non-deterministic, based on random network behaviour. So yeah until that issue is resolved, implementing this would introduce problematic behaviour that you wouldn't want to build in.

@mattvonrocketstein
Copy link

there's a race condition where 2 packages depend on a common third with compatible but different version specifiers. Whichever package is downloaded first, its specifier would be used

This problem would seem to be limited to parallel installation. If we're talking strictly about parallel downloads followed by standard, serial installs.. you might experience an extra, useless download.. but I'm not clear on why it should introduce anything nondeterministic.

I see a few potentially different scenarios for this improvement, and maybe it's useful to avoid conflating them:

  • parallel requirements downloads (single-level),
  • parallel requirements downloads (nested requirements),
  • parallel installation of requirements (as individual downloads are completed)
  • parallel installation of requirements (in some second pass, after all downloads are complete)

Of course having several of these things would be awesome, but any could be an improvement. In this thread people have raised at least 3 separate blockers from what I can tell:

  • missing requirements metadata
  • prerequisite but large-scale refactors of existing code
  • introducing nondeterminism

I'm less clear on which blockers affect which scenarios

@AraHaan
Copy link
Contributor

AraHaan commented May 17, 2017

Hmmm this is where class dicts with an entry of the downloaded dependencies names would work so then it can bite the issue about extra downloads. And then the install system can look at that dict that is only populated at run time to install all the packages in that dict. And the dict itself on every entry could have a class that stores the information needed for the install mechanism in pip. I think this can theoretically be used for parallel installs.

@ghost
Copy link

ghost commented Jul 31, 2017

What is probably the most feasible is to download all requirements in parallel until reaching a source distribution, and then download in serial. Something like:

wg = WaitGroup()
while True:
    if req is Wheel:
        wg.Add(1)
        req.download_async()
    else:
        wg.Done()
        req.download()

@pradyunsg
Copy link
Member

pradyunsg commented Aug 20, 2017

I've labelled this issue as an "deferred till PR".

This label is essentially for indicating that further discussion related to this issue should be deferred until someone comes around to make a PR. This does not mean that the said PR would be accepted - it has not been determined whether this is a useful change to pip and that decision has been deferred until the PR is made.

@pradyunsg
Copy link
Member

That said, I think it's pretty much clear that this is would be a welcome improvement. :)

@chromano
Copy link

I've written something for Python 3 using asyncio for parallel downloads and installation of wheel packages: wi.

That tool can be used with pip for fallback, i.e, when there's no wheels available and my goal is someway get that code into pip or at least to serve as motivation to move this ticket forward, since the performance gain is really noticeable.

@pradyunsg
Copy link
Member

@samuelcolvin expressed some interest in looking into this issue, over at pypa/packaging-problems#261.

@KOLANICH

This comment has been minimized.

@pradyunsg

This comment has been minimized.

@pradyunsg
Copy link
Member

pradyunsg commented Jun 5, 2019

Let's stick to the topic of adding parallel downloads to pip here.

If there are questions about the broader picture or how pip does vendoring etc, please file a new issue / start a thread on the Discourse forum etc. I've gone ahead and hidden all comments unrelated to the topic at hand.

@samuelcolvin
Copy link

The issue I created on packaging-problems pypa/packaging-problems#261 was related to parallel compiling. For me (and I imagine many other users) compiling taking way way longer than downloading.

This is particularly true:

  • on platforms where manylinux binaries don't work, eg. alpine
  • when installing packages in the cloud where one generally has a very fast connection but a relatively slow effective clock speed.

@pradyunsg due you consider this a separate concern? If so I'll create a new issue.

I guess in an ideal world pip would utilise asyncio for download, and run_in_executor with a pool of processes to compile. But we don't live in an ideal world.

In summary the question for me from this thread are:

  • where can the maximum speedup be achieved for the maximum number of people? download or compile
  • is parallel compiling closely enough related to parallel download to be implemented together? (and therefore tracked under the same issue)
  • would it be easier/more productive to build a new tool for fast installation that doesn't have the same limitations as pip? So you'd effectively do pip install fast-pip; fast-pip install -r requirements.txt.... This might make progress quicker but would leave those still using pip in the slow lane

@KOLANICH
Copy link
Contributor

KOLANICH commented Jun 5, 2019

I guess in an ideal world pip would utilise asyncio for download, and run_in_executor with a pool of processes to compile.

IMHO precompiled wheels are better solution. Compiling C++ stuff is a job of CMake and ninja, not pip.

@samuelcolvin
Copy link

samuelcolvin commented Jun 5, 2019

That's a completely different issue, I entirely agree that pre-compiled binaries would be great - but they're not at all easy apparently, see pypa/manylinux#37 and pypa/packaging-problems#69.

My suggestion is a work around that makes use of multiple cores to speed up installing/compiling many packages by ~10x.

Maybe I wasn't clear, I'm not suggesting a single package is installed using multiple threads or processes, but rather that packages can be compiled in parallel, so if I do (on alpine) pip install uvloop aiohttp pydantic cryptography pycryptodome asyncpg pycares I can use more than one core to compile those packages.

@pfmoore
Copy link
Member

pfmoore commented Jun 5, 2019

@samuelcolvin I know it's "just" a workaround, but couldn't you do something like

parallel pip wheel {} ::: uvloop aiohttp pydantic cryptography pycryptodome asyncpg pycares
pip install *.whl

Using GNU Parallel to run multiple commands in parallel - excuse me if I got the syntax wrong, I'm not a Unix user. Basically, build wheels in parallel, and then pip install them. If you have multiple machines that have the same architecture/are binary compatible, you can re-use the .whl files (pre-compiled binaries are only hard when you're distributing to machines you don't know are compatible).

@samuelcolvin
Copy link

samuelcolvin commented Jun 5, 2019

If it helps to demonstate the problem, try build the following Dockerfile with docker build .:

FROM python:3.7-alpine3.8

RUN apk add -U gcc g++ musl-dev zlib-dev libuv libffi-dev make openssl-dev

RUN pip install -U pip setuptools
RUN pip install uvloop aiohttp pydantic cryptography pycryptodome asyncpg pycares

And here's a snapshot from me running it:
Screenshot from 2019-06-05 16-08-32

You get a pretty clear idea of how little time is spent downloading compared to compiling and how little of the CPU resources are being utilised for a CPU bound task...

For me this took 137seconds, of which about 130seconds was the last line.

@samuelcolvin
Copy link

Using GNU Parallel to run multiple commands in parallel - excuse me if I got the syntax wrong, I'm not a Unix user.

Interesting idea, that might help a lot, the main problem is that multiple packages might have the same dependencies which would then be installed multiple times, even worse they might have conflicting dependencies which pip wouldn't know about in this case.

@pfmoore
Copy link
Member

pfmoore commented Jun 5, 2019

multiple packages might have the same dependencies which would then be installed multiple times, even worse they might have conflicting dependencies which pip wouldn't know about in this case.

Not if you pip install all the wheels in one command once they are built.

@samuelcolvin
Copy link

Good point, sorry.

For me that reduced the install time in the above example from 134s to 54s.

@pradyunsg
Copy link
Member

@pradyunsg due you consider this a separate concern? If so I'll create a new issue.

Yep. Even if we're going to solve them together, it's useful to have separation of concerns and discussion.

  • where can the maximum speedup be achieved for the maximum number of people? download or compile

  • is parallel compiling closely enough related to parallel download to be implemented together? (and therefore tracked under the same issue)

  • would it be easier/more productive to build a new tool for fast installation that doesn't have the same limitations as pip? So you'd effectively do pip install fast-pip; fast-pip install -r requirements.txt.... This might make progress quicker but would leave those still using pip in the slow lane

I don't know. Someone has to do the work of figuring out what the scope of things is and actually spend time implementing this stuff.

Since no one has stepped up to do it yet, and the volunteered time from the maintainers isn't going to be all pulled into this issue, I don't think that we'll have the answers soon either.

@pradyunsg
Copy link
Member

@thedrow please feel free to file a PR and we can have more concrete discussions on it. :)

@jsar3004
Copy link

jsar3004 commented Jul 2, 2020

I am beginner to open-source please help what technologies should I learn so that I can do this project?

@McSinyx
Copy link
Contributor

McSinyx commented Jul 2, 2020

Hi @jsar3004, I'm working on this with pip's maintainers and contributors this summer. Turns out, it is not a trivial task as it touches many corner that I never thought that it would. FYI the current work is implementing GH-7819 and if you want to help out, feel free to join the discussion.

If you are really beginning to contributing free software development, it's more than just technical skills and understanding that you should possess. For medium-large-sized project like pip, a decent amount of time is actually spent in discussion of all level (from idea to nitpicking)—trust me I'm still learning on how to do it properly. Personally I suggest starting from the list of issues awaiting PRs and see what you're also interested in. Increasing the scope a bit, I believe others project under the hood of PyPA also welcome people looking to contribute.

@KOLANICH
Copy link
Contributor

KOLANICH commented Jul 2, 2020

@pradyunsg
Copy link
Member

I am beginner to open-source please help what technologies should I learn so that I can do this project?

@jsar3004 Hi! I suggest you get started by looking at other tasks, such as good first issues in pip and [other PyPA tooling](org:pypa label:"good first issue"). This is a significantly large task, with multiple moving parts -- it would not be possible to work on this without an initial ramp-up, gaining a better understanding of how pip works and what pip's development workflows are.

@cosmicexplorer
Copy link
Contributor

Hey, I've created a PR #12923 to do this by extending the BatchDownloader already developed by @McSinyx. It's not quite ready for primetime yet, as I'm not sure how to support the progress bar. The BatchDownloader interface (producing an iterable of results) made it super easy to install in parallel with downloads as requested, but the parallelism will be limited to "metadata-only" dists, which require the rest of the work in #12921 to be available for the majority of pip download targets.

Please ask questions/comments in #12921 regarding metadata resolves, and #12923 regarding the parallel download strategy (I just used threads instead of any fancy async stuff). I haven't figured out the progress bar yet (although I'm going to try to do so now), so please feel free to leave comments in #12923 regarding how to support a unified progress bar for parallel downloads.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
resolution: deferred till PR Further discussion will happen when a PR is made type: enhancement Improvements to functionality
Projects
None yet
Development

No branches or pull requests