diff --git a/IPython/kernel/multiengineclient.py b/IPython/kernel/multiengineclient.py index 1d27037..2f7ad16 100644 --- a/IPython/kernel/multiengineclient.py +++ b/IPython/kernel/multiengineclient.py @@ -885,7 +885,62 @@ class FullBlockingMultiEngineClient(InteractiveMultiEngineClient): targets, block = self._findTargetsAndBlock(targets, block) return self._blockFromThread(self.smultiengine.run, filename, targets=targets, block=block) + + def benchmark(self, push_size=10000): + """ + Run performance benchmarks for the current IPython cluster. + + This method tests both the latency of sending command and data to the + engines as well as the throughput of sending large objects to the + engines using push. The latency is measured by having one or more + engines execute the command 'pass'. The throughput is measure by + sending an NumPy array of size `push_size` to one or more engines. + + These benchmarks will vary widely on different hardware and networks + and thus can be used to get an idea of the performance characteristics + of a particular configuration of an IPython controller and engines. + + This function is not testable within our current testing framework. + """ + import timeit, __builtin__ + __builtin__._mec_self = self + benchmarks = {} + repeat = 3 + count = 10 + + timer = timeit.Timer('_mec_self.execute("pass",0)') + result = 1000*min(timer.repeat(repeat,count))/count + benchmarks['single_engine_latency'] = (result,'msec') + + timer = timeit.Timer('_mec_self.execute("pass")') + result = 1000*min(timer.repeat(repeat,count))/count + benchmarks['all_engine_latency'] = (result,'msec') + try: + import numpy as np + except: + pass + else: + timer = timeit.Timer( + "_mec_self.push(d)", + "import numpy as np; d = dict(a=np.zeros(%r,dtype='float64'))" % push_size + ) + result = min(timer.repeat(repeat,count))/count + benchmarks['all_engine_push'] = (1e-6*push_size*8/result, 'MB/sec') + + try: + import numpy as np + except: + pass + else: + timer = timeit.Timer( + "_mec_self.push(d,0)", + "import numpy as np; d = dict(a=np.zeros(%r,dtype='float64'))" % push_size + ) + result = min(timer.repeat(repeat,count))/count + benchmarks['single_engine_push'] = (1e-6*push_size*8/result, 'MB/sec') + + return benchmarks components.registerAdapter(FullBlockingMultiEngineClient, diff --git a/IPython/kernel/scripts/ipcluster.py b/IPython/kernel/scripts/ipcluster.py index 640a41a..ca20901 100755 --- a/IPython/kernel/scripts/ipcluster.py +++ b/IPython/kernel/scripts/ipcluster.py @@ -478,15 +478,31 @@ Try running ipcluster with the -xy flags: ipcluster local -xy -n 4""") cont_args.append('-y') return True +def check_reuse(args, cont_args): + if args.r: + cont_args.append('-r') + if args.client_port == 0 or args.engine_port == 0: + log.err(""" +To reuse FURL files, you must also set the client and engine ports using +the --client-port and --engine-port options.""") + reactor.stop() + return False + cont_args.append('--client-port=%i' % args.client_port) + cont_args.append('--engine-port=%i' % args.engine_port) + return True def main_local(args): cont_args = [] cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller')) - + # Check security settings before proceeding if not check_security(args, cont_args): return - + + # See if we are reusing FURL files + if not check_reuse(args, cont_args): + return + cl = ControllerLauncher(extra_args=cont_args) dstart = cl.start() def start_engines(cont_pid): @@ -513,18 +529,22 @@ def main_local(args): dstart.addErrback(lambda f: f.raiseException()) -def main_mpirun(args): +def main_mpi(args): cont_args = [] cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller')) - + # Check security settings before proceeding if not check_security(args, cont_args): return - + + # See if we are reusing FURL files + if not check_reuse(args, cont_args): + return + cl = ControllerLauncher(extra_args=cont_args) dstart = cl.start() def start_engines(cont_pid): - raw_args = ['mpirun'] + raw_args = [args.cmd] raw_args.extend(['-n',str(args.n)]) raw_args.append('ipengine') raw_args.append('-l') @@ -554,11 +574,15 @@ def main_mpirun(args): def main_pbs(args): cont_args = [] cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller')) - + # Check security settings before proceeding if not check_security(args, cont_args): return - + + # See if we are reusing FURL files + if not check_reuse(args, cont_args): + return + cl = ControllerLauncher(extra_args=cont_args) dstart = cl.start() def start_engines(r): @@ -598,13 +622,16 @@ def main_ssh(args): if not check_security(args, cont_args): return + # See if we are reusing FURL files + if not check_reuse(args, cont_args): + return + cl = ControllerLauncher(extra_args=cont_args) dstart = cl.start() def start_engines(cont_pid): ssh_set = SSHEngineSet(clusterfile['engines'], sshx=args.sshx) def shutdown(signum, frame): d = ssh_set.kill() - # d.addErrback(log.err) cl.interrupt_then_kill(1.0) reactor.callLater(2.0, reactor.stop) signal.signal(signal.SIGINT,shutdown) @@ -621,6 +648,26 @@ def main_ssh(args): def get_args(): base_parser = argparse.ArgumentParser(add_help=False) base_parser.add_argument( + '-r', + action='store_true', + dest='r', + help='try to reuse FURL files. Use with --client-port and --engine-port' + ) + base_parser.add_argument( + '--client-port', + type=int, + dest='client_port', + help='the port the controller will listen on for client connections', + default=0 + ) + base_parser.add_argument( + '--engine-port', + type=int, + dest='engine_port', + help='the port the controller will listen on for engine connections', + default=0 + ) + base_parser.add_argument( '-x', action='store_true', dest='x', @@ -665,7 +712,7 @@ def get_args(): parser_mpirun = subparsers.add_parser( 'mpirun', - help='run a cluster using mpirun', + help='run a cluster using mpirun (mpiexec also works)', parents=[base_parser] ) parser_mpirun.add_argument( @@ -674,7 +721,20 @@ def get_args(): dest="mpi", # Don't put a default here to allow no MPI support help="how to call MPI_Init (default=mpi4py)" ) - parser_mpirun.set_defaults(func=main_mpirun) + parser_mpirun.set_defaults(func=main_mpi, cmd='mpirun') + + parser_mpiexec = subparsers.add_parser( + 'mpiexec', + help='run a cluster using mpiexec (mpirun also works)', + parents=[base_parser] + ) + parser_mpiexec.add_argument( + "--mpi", + type=str, + dest="mpi", # Don't put a default here to allow no MPI support + help="how to call MPI_Init (default=mpi4py)" + ) + parser_mpiexec.set_defaults(func=main_mpi, cmd='mpiexec') parser_pbs = subparsers.add_parser( 'pbs', diff --git a/docs/source/changes.txt b/docs/source/changes.txt index 2408bbf..c0e146e 100644 --- a/docs/source/changes.txt +++ b/docs/source/changes.txt @@ -75,6 +75,9 @@ Bug fixes The block is assigned to pasted_block even if code raises exception. +* Bug #274067 'The code in get_home_dir is broken for py2exe' was + fixed. + Backwards incompatible changes ------------------------------ diff --git a/docs/source/parallel/parallel_mpi.txt b/docs/source/parallel/parallel_mpi.txt index d09bf44..4df70f3 100644 --- a/docs/source/parallel/parallel_mpi.txt +++ b/docs/source/parallel/parallel_mpi.txt @@ -32,34 +32,34 @@ Starting the engines with MPI enabled To use code that calls MPI, there are typically two things that MPI requires. 1. The process that wants to call MPI must be started using - :command:`mpirun` or a batch system (like PBS) that has MPI support. + :command:`mpiexec` or a batch system (like PBS) that has MPI support. 2. Once the process starts, it must call :func:`MPI_Init`. There are a couple of ways that you can start the IPython engines and get these things to happen. -Automatic starting using :command:`mpirun` and :command:`ipcluster` +Automatic starting using :command:`mpiexec` and :command:`ipcluster` ------------------------------------------------------------------- -The easiest approach is to use the `mpirun` mode of :command:`ipcluster`, which will first start a controller and then a set of engines using :command:`mpirun`:: +The easiest approach is to use the `mpiexec` mode of :command:`ipcluster`, which will first start a controller and then a set of engines using :command:`mpiexec`:: - $ ipcluster mpirun -n 4 + $ ipcluster mpiexec -n 4 This approach is best as interrupting :command:`ipcluster` will automatically stop and clean up the controller and engines. -Manual starting using :command:`mpirun` +Manual starting using :command:`mpiexec` --------------------------------------- -If you want to start the IPython engines using the :command:`mpirun`, just do:: +If you want to start the IPython engines using the :command:`mpiexec`, just do:: - $ mpirun -n 4 ipengine --mpi=mpi4py + $ mpiexec -n 4 ipengine --mpi=mpi4py This requires that you already have a controller running and that the FURL files for the engines are in place. We also have built in support for PyTrilinos [PyTrilinos]_, which can be used (assuming is installed) by starting the engines with:: - mpirun -n 4 ipengine --mpi=pytrilinos + mpiexec -n 4 ipengine --mpi=pytrilinos Automatic starting using PBS and :command:`ipcluster` ----------------------------------------------------- @@ -84,7 +84,7 @@ First, lets define a simply function that uses MPI to calculate the sum of a dis Now, start an IPython cluster in the same directory as :file:`psum.py`:: - $ ipcluster mpirun -n 4 + $ ipcluster mpiexec -n 4 Finally, connect to the cluster and use this function interactively. In this case, we create a random array on each engine and sum up all the random arrays using our :func:`psum` function: diff --git a/docs/source/parallel/parallel_process.txt b/docs/source/parallel/parallel_process.txt index d35ffc9..3884d89 100644 --- a/docs/source/parallel/parallel_process.txt +++ b/docs/source/parallel/parallel_process.txt @@ -85,33 +85,40 @@ To see other command line options for the local mode, do:: $ ipcluster local -h -Using :command:`ipcluster` in mpirun mode ------------------------------------------ +Using :command:`ipcluster` in mpiexec/mpirun mode +------------------------------------------------- -The mpirun mode is useful if you: +The mpiexec/mpirun mode is useful if you: 1. Have MPI installed. -2. Your systems are configured to use the :command:`mpirun` command to start - processes. +2. Your systems are configured to use the :command:`mpiexec` or + :command:`mpirun` commands to start MPI processes. + +.. note:: + + The preferred command to use is :command:`mpiexec`. However, we also + support :command:`mpirun` for backwards compatibility. The underlying + logic used is exactly the same, the only difference being the name of the + command line program that is called. If these are satisfied, you can start an IPython cluster using:: - $ ipcluster mpirun -n 4 + $ ipcluster mpiexec -n 4 This does the following: 1. Starts the IPython controller on current host. -2. Uses :command:`mpirun` to start 4 engines. +2. Uses :command:`mpiexec` to start 4 engines. On newer MPI implementations (such as OpenMPI), this will work even if you don't make any calls to MPI or call :func:`MPI_Init`. However, older MPI implementations actually require each process to call :func:`MPI_Init` upon starting. The easiest way of having this done is to install the mpi4py [mpi4py]_ package and then call ipcluster with the ``--mpi`` option:: - $ ipcluster mpirun -n 4 --mpi=mpi4py + $ ipcluster mpiexec -n 4 --mpi=mpi4py Unfortunately, even this won't work for some MPI implementations. If you are having problems with this, you will likely have to use a custom Python executable that itself calls :func:`MPI_Init` at the appropriate time. Fortunately, mpi4py comes with such a custom Python executable that is easy to install and use. However, this custom Python executable approach will not work with :command:`ipcluster` currently. Additional command line options for this mode can be found by doing:: - $ ipcluster mpirun -h + $ ipcluster mpiexec -h More details on using MPI with IPython can be found :ref:`here `. @@ -301,6 +308,11 @@ This is possible. The only thing you have to do is decide what ports the contro $ ipcontroller -r --client-port=10101 --engine-port=10102 +These options also work with all of the various modes of +:command:`ipcluster`:: + + $ ipcluster local -n 2 -r --client-port=10101 --engine-port=10102 + Then, just copy the furl files over the first time and you are set. You can start and stop the controller and engines any many times as you want in the future, just make sure to tell the controller to use the *same* ports. .. note::