From 3d28440b08a51594d9058b2045254c7c85e016a1 Mon Sep 17 00:00:00 2001 From: Yanzhe Chen Date: Fri, 25 Oct 2013 10:12:57 +0800 Subject: [PATCH 01/50] hybrid ingress, hybrid ginger ingress and powerlyra engine --- src/graphlab/engine/omni_engine.hpp | 9 + src/graphlab/engine/powerlyra_sync_engine.hpp | 2097 +++++++++++++++++ src/graphlab/graph/builtin_parsers.hpp | 70 +- src/graphlab/graph/distributed_graph.hpp | 147 +- .../distributed_hybrid_ginger_ingress.hpp | 857 +++++++ .../ingress/distributed_hybrid_ingress.hpp | 817 +++++++ .../ingress/distributed_ingress_base.hpp | 5 + .../graph/ingress/ingress_edge_decision.hpp | 43 + src/graphlab/util/tetrad.hpp | 122 + src/graphlab/util/triple.hpp | 112 + 10 files changed, 4265 insertions(+), 14 deletions(-) create mode 100644 src/graphlab/engine/powerlyra_sync_engine.hpp create mode 100644 src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp create mode 100644 src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp create mode 100755 src/graphlab/util/tetrad.hpp create mode 100755 src/graphlab/util/triple.hpp diff --git a/src/graphlab/engine/omni_engine.hpp b/src/graphlab/engine/omni_engine.hpp index 1d229e04d6..71ddc8cf96 100644 --- a/src/graphlab/engine/omni_engine.hpp +++ b/src/graphlab/engine/omni_engine.hpp @@ -31,6 +31,7 @@ #include #include #include +#include namespace graphlab { @@ -136,6 +137,11 @@ namespace graphlab { */ typedef async_consistent_engine async_consistent_engine_type; + /** + * \brief the type of powerlyra synchronous engine + */ + typedef powerlyra_sync_engine powerlyra_sync_engine_type; + private: @@ -193,6 +199,9 @@ namespace graphlab { } else if(engine_type == "async" || engine_type == "asynchronous") { logstream(LOG_INFO) << "Using the Synchronous engine." << std::endl; engine_ptr = new async_consistent_engine_type(dc, graph, new_options); + } else if(engine_type == "plsync" || engine_type == "powerlyra_synchronous") { + logstream(LOG_INFO) << "Using the PowerLyra Synchronous engine." << std::endl; + engine_ptr = new powerlyra_sync_engine_type(dc, graph, new_options); } else { logstream(LOG_FATAL) << "Invalid engine type: " << engine_type << std::endl; } diff --git a/src/graphlab/engine/powerlyra_sync_engine.hpp b/src/graphlab/engine/powerlyra_sync_engine.hpp new file mode 100644 index 0000000000..0e3b5f1607 --- /dev/null +++ b/src/graphlab/engine/powerlyra_sync_engine.hpp @@ -0,0 +1,2097 @@ +/** + * Copyright (c) 2009 Carnegie Mellon University. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * For more about this software visit: + * + * http://www.graphlab.ml.cmu.edu + * + */ + + + +#ifndef GRAPHLAB_POWERLYRA_SYNC_ENGINE_HPP +#define GRAPHLAB_POWERLYRA_SYNC_ENGINE_HPP + +#include +#include + +#include + +#include +#include +#include + +#include +#include + + + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + + + + + +#include + +#define ALIGN_DOWN(_n, _w) ((_n) & (~((_w)-1))) + +namespace graphlab { + + + /** + * \ingroup engines + * + * \brief The synchronous engine executes all active vertex program + * synchronously in a sequence of super-step (iterations) in both the + * shared and distributed memory settings. + * + * \tparam VertexProgram The user defined vertex program which + * should implement the \ref graphlab::ivertex_program interface. + * + * + * ### Execution Semantics + * + * On start() the \ref graphlab::ivertex_program::init function is invoked + * on all vertex programs in parallel to initialize the vertex program, + * vertex data, and possibly signal vertices. + * The engine then proceeds to execute a sequence of + * super-steps (iterations) each of which is further decomposed into a + * sequence of minor-steps which are also executed synchronously: + * \li Receive all incoming messages (signals) by invoking the + * \ref graphlab::ivertex_program::init function on all + * vertex-programs that have incoming messages. If a + * vertex-program does not have any incoming messages then it is + * not active during this super-step. + * \li Execute all gathers for active vertex programs by invoking + * the user defined \ref graphlab::ivertex_program::gather function + * on the edge direction returned by the + * \ref graphlab::ivertex_program::gather_edges function. The gather + * functions can modify edge data but cannot modify the vertex + * program or vertex data and therefore can be executed on multiple + * edges in parallel. The gather type is used to accumulate (sum) + * the result of the gather function calls. + * \li Execute all apply functions for active vertex-programs by + * invoking the user defined \ref graphlab::ivertex_program::apply + * function passing the sum of the gather functions. If \ref + * graphlab::ivertex_program::gather_edges returns no edges then + * the default gather value is passed to apply. The apply function + * can modify the vertex program and vertex data. + * \li Execute all scatters for active vertex programs by invoking + * the user defined \ref graphlab::ivertex_program::scatter function + * on the edge direction returned by the + * \ref graphlab::ivertex_program::scatter_edges function. The scatter + * functions can modify edge data but cannot modify the vertex + * program or vertex data and therefore can be executed on multiple + * edges in parallel. + * + * ### Construction + * + * The synchronous engine is constructed by passing in a + * \ref graphlab::distributed_control object which manages coordination + * between engine threads and a \ref graphlab::distributed_graph object + * which is the graph on which the engine should be run. The graph should + * already be populated and cannot change after the engine is constructed. + * In the distributed setting all program instances (running on each machine) + * should construct an instance of the engine at the same time. + * + * Computation is initiated by signaling vertices using either + * \ref graphlab::powerlyra_sync_engine::signal or + * \ref graphlab::powerlyra_sync_engine::signal_all. In either case all + * machines should invoke signal or signal all at the same time. Finally, + * computation is initiated by calling the + * \ref graphlab::powerlyra_sync_engine::start function. + * + * ### Example Usage + * + * The following is a simple example demonstrating how to use the engine: + * \code + * #include + * + * struct vertex_data { + * // code + * }; + * struct edge_data { + * // code + * }; + * typedef graphlab::distributed_graph graph_type; + * typedef float gather_type; + * struct pagerank_vprog : + * public graphlab::ivertex_program { + * // code + * }; + * + * int main(int argc, char** argv) { + * // Initialize control plain using mpi + * graphlab::mpi_tools::init(argc, argv); + * graphlab::distributed_control dc; + * // Parse command line options + * graphlab::command_line_options clopts("PageRank algorithm."); + * std::string graph_dir; + * clopts.attach_option("graph", &graph_dir, graph_dir, + * "The graph file."); + * if(!clopts.parse(argc, argv)) { + * std::cout << "Error in parsing arguments." << std::endl; + * return EXIT_FAILURE; + * } + * graph_type graph(dc, clopts); + * graph.load_structure(graph_dir, "tsv"); + * graph.finalize(); + * std::cout << "#vertices: " << graph.num_vertices() + * << " #edges:" << graph.num_edges() << std::endl; + * graphlab::powerlyra_sync_engine engine(dc, graph, clopts); + * engine.signal_all(); + * engine.start(); + * std::cout << "Runtime: " << engine.elapsed_time(); + * graphlab::mpi_tools::finalize(); + * } + * \endcode + * + * + * + * Engine Options + * ===================== + * The synchronous engine supports several engine options which can + * be set as command line arguments using \c --engine_opts : + * + * \li max_iterations: (default: infinity) The maximum number + * of iterations (super-steps) to run. + * + * \li timeout: (default: infinity) The maximum time in + * seconds that the engine may run. When the time runs out the + * current iteration is completed and then the engine terminates. + * + * \li use_cache: (default: false) This is used to enable + * caching. When caching is enabled the gather phase is skipped for + * vertices that already have a cached value. To use caching the + * vertex program must either clear (\ref icontext::clear_gather_cache) + * or update (\ref icontext::post_delta) the cache values of + * neighboring vertices during the scatter phase. + * + * \li \b snapshot_interval If set to a positive value, a snapshot + * is taken every this number of iterations. If set to 0, a snapshot + * is taken before the first iteration. If set to a negative value, + * no snapshots are taken. Defaults to -1. A snapshot is a binary + * dump of the graph. + * + * \li \b snapshot_path If snapshot_interval is set to a value >=0, + * this option must be specified and should contain a target basename + * for the snapshot. The path including folder and file prefix in + * which the snapshots should be saved. + * + * \see graphlab::omni_engine + * \see graphlab::async_consistent_engine + * \see graphlab::semi_synchronous_engine + * \see graphlab::power_sync_engine + * \see graphlab::lyra_sync_engine + * \see graphlab::powerlyra_sync_engine + */ + template + class powerlyra_sync_engine : + public iengine { + + public: + /** + * \brief The user defined vertex program type. Equivalent to the + * VertexProgram template argument. + * + * The user defined vertex program type which should implement the + * \ref graphlab::ivertex_program interface. + */ + typedef VertexProgram vertex_program_type; + + /** + * \brief The user defined type returned by the gather function. + * + * The gather type is defined in the \ref graphlab::ivertex_program + * interface and is the value returned by the + * \ref graphlab::ivertex_program::gather function. The + * gather type must have an operator+=(const gather_type& + * other) function and must be \ref sec_serializable. + */ + typedef typename VertexProgram::gather_type gather_type; + + + /** + * \brief The user defined message type used to signal neighboring + * vertex programs. + * + * The message type is defined in the \ref graphlab::ivertex_program + * interface and used in the call to \ref graphlab::icontext::signal. + * The message type must have an + * operator+=(const gather_type& other) function and + * must be \ref sec_serializable. + */ + typedef typename VertexProgram::message_type message_type; + + /** + * \brief The type of data associated with each vertex in the graph + * + * The vertex data type must be \ref sec_serializable. + */ + typedef typename VertexProgram::vertex_data_type vertex_data_type; + + /** + * \brief The type of data associated with each edge in the graph + * + * The edge data type must be \ref sec_serializable. + */ + typedef typename VertexProgram::edge_data_type edge_data_type; + + /** + * \brief The type of graph supported by this vertex program + * + * See graphlab::distributed_graph + */ + typedef typename VertexProgram::graph_type graph_type; + + /** + * \brief The type used to represent a vertex in the graph. + * See \ref graphlab::distributed_graph::vertex_type for details + * + * The vertex type contains the function + * \ref graphlab::distributed_graph::vertex_type::data which + * returns a reference to the vertex data as well as other functions + * like \ref graphlab::distributed_graph::vertex_type::num_in_edges + * which returns the number of in edges. + * + */ + typedef typename graph_type::vertex_type vertex_type; + + /** + * \brief The type used to represent an edge in the graph. + * See \ref graphlab::distributed_graph::edge_type for details. + * + * The edge type contains the function + * \ref graphlab::distributed_graph::edge_type::data which returns a + * reference to the edge data. In addition the edge type contains + * the function \ref graphlab::distributed_graph::edge_type::source and + * \ref graphlab::distributed_graph::edge_type::target. + * + */ + typedef typename graph_type::edge_type edge_type; + + /** + * \brief The type of the callback interface passed by the engine to vertex + * programs. See \ref graphlab::icontext for details. + * + * The context callback is passed to the vertex program functions and is + * used to signal other vertices, get the current iteration, and access + * information about the engine. + */ + typedef icontext icontext_type; + + private: + + /** + * \brief Local vertex type used by the engine for fast indexing + */ + typedef typename graph_type::local_vertex_type local_vertex_type; + + /** + * \brief Local edge type used by the engine for fast indexing + */ + typedef typename graph_type::local_edge_type local_edge_type; + + /** + * \brief Local vertex id type used by the engine for fast indexing + */ + typedef typename graph_type::lvid_type lvid_type; + + std::vector per_thread_compute_time; + /** + * \brief The actual instance of the context type used by this engine. + */ + typedef context context_type; + friend class context; + + + /** + * \brief The type of the distributed aggregator inherited from iengine + */ + typedef typename iengine::aggregator_type aggregator_type; + + /** + * \brief The object used to communicate with remote copies of the + * synchronous engine. + */ + dc_dist_object< powerlyra_sync_engine > rmi; + + /** + * \brief A reference to the distributed graph on which this + * synchronous engine is running. + */ + graph_type& graph; + + /** + * \brief The number of CPUs used. + */ + size_t ncpus; + + /** + * \brief The local worker threads used by this engine + */ + fiber_group threads; + + /** + * \brief A thread barrier that is used to control the threads in the + * thread pool. + */ + fiber_barrier thread_barrier; + + /** + * \brief The maximum number of super-steps (iterations) to run + * before terminating. If the max iterations is reached the + * engine will terminate if their are no messages remaining. + */ + size_t max_iterations; + + + /* + * \brief When caching is enabled the gather phase is skipped for + * vertices that already have a cached value. To use caching the + * vertex program must either clear (\ref icontext::clear_gather_cache) + * or update (\ref icontext::post_delta) the cache values of + * neighboring vertices during the scatter phase. + */ + bool use_cache; + + /** + * \brief A snapshot is taken every this number of iterations. + * If snapshot_interval == 0, a snapshot is only taken before the first + * iteration. If snapshot_interval < 0, no snapshots are taken. + */ + int snapshot_interval; + + /// \brief The target base name the snapshot is saved in. + std::string snapshot_path; + + /** + * \brief A counter that tracks the current iteration number since + * start was last invoked. + */ + size_t iteration_counter; + + /** + * \brief The time in seconds at which the engine started. + */ + float start_time; + + /** + * \brief The total execution time. + */ + double exec_time; + + /** + * \brief The time spends on exch-msgs phase. + */ + double exch_time; + + /** + * \brief The time spends on recv-msgs phase. + */ + double recv_time; + + /** + * \brief The time spends on gather phase. + */ + double gather_time; + + /** + * \brief The time spends on apply phase. + */ + double apply_time; + + /** + * \brief The time spends on scatter phase. + */ + double scatter_time; + + /** + * \brief The interval time to print status. + */ + float print_interval; + + /** + * \brief The timeout time in seconds + */ + float timeout; + + /** + * \brief Schedules all vertices every iteration + */ + bool sched_allv; + + /** + * \brief Used to stop the engine prematurely + */ + bool force_abort; + + /** + * \brief The vertex locks protect access to vertex specific + * data-structures including + * \ref graphlab::powerlyra_sync_engine::gather_accum + * and \ref graphlab::powerlyra_sync_engine::messages. + */ + std::vector vlocks; + + /** + * \brief The egde dirs associated with each vertex on this + * machine. + */ + std::vector edge_dirs; + + /** + * \brief The vertex programs associated with each vertex on this + * machine. + */ + std::vector vertex_programs; + + /** + * \brief Vector of messages associated with each vertex. + */ + std::vector messages; + + /** + * \brief Bit indicating whether a message is present for each vertex. + */ + dense_bitset has_message; + + + /** + * \brief Gather accumulator used for each master vertex to merge + * the result of all the machine specific accumulators (or + * caches). + * + * The gather accumulator can be accessed by multiple threads at + * once and therefore must be guarded by a vertex locks in + * \ref graphlab::powerlyra_sync_engine::vlocks + */ + std::vector gather_accum; + + /** + * \brief Bit indicating if the gather has accumulator contains any + * values. + * + * While dense bitsets are thread safe the value of this bit must + * change concurrently with the + * \ref graphlab::powerlyra_sync_engine::gather_accum and therefore is + * set while holding the lock in + * \ref graphlab::powerlyra_sync_engine::vlocks. + */ + dense_bitset has_gather_accum; + + + /** + * \brief This optional vector contains caches of previous gather + * contributions for each machine. + * + * Caching is done locally and therefore a high-degree vertex may + * have multiple caches (one per machine). + */ + std::vector gather_cache; + + /** + * \brief A bit indicating if the local gather for that vertex is + * available. + */ + dense_bitset has_cache; + + /** + * \brief A bit (for master vertices) indicating if that vertex is active + * (received a message on this iteration). + */ + dense_bitset active_superstep; + + /** + * \brief The number of local vertices (masters) that are active on this + * iteration. + */ + atomic num_active_vertices; + + /** + * \brief A bit indicating (for all vertices) whether to + * participate in the current minor-step (gather or scatter). + */ + dense_bitset active_minorstep; + + /** + * \brief A counter measuring the number of gathers that have been completed + */ + atomic completed_gathers; + + /** + * \brief A counter measuring the number of applys that have been completed + */ + atomic completed_applys; + + /** + * \brief A counter measuring the number of scatters that have been completed + */ + atomic completed_scatters; + + + /** + * \brief The shared counter used coordinate operations between + * threads. + */ + atomic shared_lvid_counter; + + /** + * \brief The engine type used to create express. + */ + typedef powerlyra_sync_engine engine_type; + + /** + * \brief The triple type used to activate mirrors. + */ + typedef triple + vid_vprog_edir_triple_type; + + /** + * \brief The type of the express used to activate mirrors + */ + typedef fiber_buffered_exchange + activ_exchange_type; + + /** + * \brief The type of buffer used by the express to activate mirrors + */ + typedef typename activ_exchange_type::buffer_type activ_buffer_type; + + /** + * \brief The distributed express used to activate mirrors + * vertex programs. + */ + activ_exchange_type activ_exchange; + + /** + * \brief The tetrad type used to update vertex data and activate mirrors. + */ + typedef tetrad vid_vdata_vprog_edir_tetrad_type; + + /** + * \brief The type of the express used to update mirrors + */ + typedef fiber_buffered_exchange + update_exchange_type; + + /** + * \brief The type of buffer used by the express to update mirrors + */ + typedef typename update_exchange_type::buffer_type update_buffer_type; + + /** + * \brief The distributed express used to update mirrors + * vertex programs. + */ + update_exchange_type update_exchange; + + /** + * \brief The pair type used to synchronize the results of the gather phase + */ + typedef std::pair vid_gather_pair_type; + + /** + * \brief The type of the exchange used to synchronize accums + * accumulators + */ + typedef fiber_buffered_exchange accum_exchange_type; + + /** + * \brief The distributed exchange used to synchronize accums + * accumulators. + */ + accum_exchange_type accum_exchange; + + /** + * \brief The pair type used to synchronize messages + */ + typedef std::pair vid_message_pair_type; + + /** + * \brief The type of the exchange used to synchronize messages + */ + typedef fiber_buffered_exchange message_exchange_type; + + /** + * \brief The distributed exchange used to synchronize messages + */ + message_exchange_type message_exchange; + + + /** + * \brief The distributed aggregator used to manage background + * aggregation. + */ + aggregator_type aggregator; + + DECLARE_EVENT(EVENT_APPLIES); + DECLARE_EVENT(EVENT_GATHERS); + DECLARE_EVENT(EVENT_SCATTERS); + DECLARE_EVENT(EVENT_ACTIVE_CPUS); + public: + + /** + * \brief Construct a synchronous engine for a given graph and options. + * + * The synchronous engine should be constructed after the graph + * has been loaded (e.g., \ref graphlab::distributed_graph::load) + * and the graphlab options have been set + * (e.g., \ref graphlab::command_line_options). + * + * In the distributed engine the synchronous engine must be called + * on all machines at the same time (in the same order) passing + * the \ref graphlab::distributed_control object. Upon + * construction the synchronous engine allocates several + * data-structures to store messages, gather accumulants, and + * vertex programs and therefore may require considerable memory. + * + * The number of threads to create are read from + * \ref graphlab_options::get_ncpus "opts.get_ncpus()". + * + * See the main class documentation + * for details on the available options. + * + * @param [in] dc Distributed controller to associate with + * @param [in,out] graph A reference to the graph object that this + * engine will modify. The graph must be fully constructed and + * finalized. + * @param [in] opts A graphlab::graphlab_options object specifying engine + * parameters. This is typically constructed using + * \ref graphlab::command_line_options. + */ + powerlyra_sync_engine(distributed_control& dc, graph_type& graph, + const graphlab_options& opts = graphlab_options()); + + + /** + * \brief Start execution of the synchronous engine. + * + * The start function begins computation and does not return until + * there are no remaining messages or until max_iterations has + * been reached. + * + * The start() function modifies the data graph through the vertex + * programs and so upon return the data graph should contain the + * result of the computation. + * + * @return The reason for termination + */ + execution_status::status_enum start(); + + // documentation inherited from iengine + size_t num_updates() const; + + // documentation inherited from iengine + void signal(vertex_id_type vid, + const message_type& message = message_type()); + + // documentation inherited from iengine + void signal_all(const message_type& message = message_type(), + const std::string& order = "shuffle"); + + void signal_vset(const vertex_set& vset, + const message_type& message = message_type(), + const std::string& order = "shuffle"); + + + // documentation inherited from iengine + float elapsed_seconds() const; + + // documentation inherited from iengine + double execution_time() const; + + + /** + * \brief Get the current iteration number since start was last + * invoked. + * + * \return the current iteration + */ + int iteration() const; + + + /** + * \brief Compute the total memory used by the entire distributed + * system. + * + * @return The total memory used in bytes. + */ + size_t total_memory_usage() const; + + /** + * \brief Get a pointer to the distributed aggregator object. + * + * This is currently used by the \ref graphlab::iengine interface to + * implement the calls to aggregation. + * + * @return a pointer to the local aggregator. + */ + aggregator_type* get_aggregator(); + + /** + * \brief Initialize the engine and allocate datastructures for vertex, and lock, + * clear all the messages. + */ + void init(); + + + private: + + + /** + * \brief Resize the datastructures to fit the graph size (in case of dynamic graph). Keep all the messages + * and caches. + */ + void resize(); + + /** + * \brief This internal stop function is called by the \ref graphlab::context to + * terminate execution of the engine. + */ + void internal_stop(); + + /** + * \brief This function is called remote by the rpc to force the + * engine to stop. + */ + void rpc_stop(); + + /** + * \brief Signal a vertex. + * + * This function is called by the \ref graphlab::context. + * + * @param [in] lvid the local vertex id of the vertex to signal + * @param [in] message the message to send to that vertex. + */ + void internal_signal(const vertex_type& vertex, + const message_type& message); + + void internal_signal(const vertex_type& vertex); + + /** + * \brief Called by the context to signal an arbitrary vertex. + * This must be done by finding the owner of that vertex. + * + * @param [in] gvid the global vertex id of the vertex to signal + * @param [in] message the message to send to that vertex. + */ + void internal_signal_gvid(vertex_id_type gvid, + const message_type& message = message_type()); + + /** + * \brief This function tests if this machine is the master of + * gvid and signals if successful. + */ + void internal_signal_rpc(vertex_id_type gvid, + const message_type& message = message_type()); + + + /** + * \brief Post a to a previous gather for a give vertex. + * + * This function is called by the \ref graphlab::context. + * + * @param [in] vertex The vertex to which to post a change in the sum + * @param [in] delta The change in that sum + */ + void internal_post_delta(const vertex_type& vertex, + const gather_type& delta); + + /** + * \brief Clear the cached gather for a vertex if one is + * available. + * + * This function is called by the \ref graphlab::context. + * + * @param [in] vertex the vertex for which to clear the cache + */ + void internal_clear_gather_cache(const vertex_type& vertex); + + + // Program Steps ========================================================== + + + void thread_launch_wrapped_event_counter(boost::function fn) { + INCREMENT_EVENT(EVENT_ACTIVE_CPUS, 1); + fn(); + DECREMENT_EVENT(EVENT_ACTIVE_CPUS, 1); + } + + /** + * \brief Executes ncpus copies of a member function each with a + * unique consecutive id (thread id). + * + * This function is used by the main loop to execute each of the + * stages in parallel. + * + * The member function must have the type: + * + * \code + * void powerlyra_sync_engine::member_fun(size_t threadid); + * \endcode + * + * This function runs an rmi barrier after termination + * + * @tparam the type of the member function. + * @param [in] member_fun the function to call. + */ + template + void run_synchronous(MemberFunction member_fun) { + shared_lvid_counter = 0; + if (ncpus <= 1) { + INCREMENT_EVENT(EVENT_ACTIVE_CPUS, 1); + } + // launch the initialization threads + for(size_t i = 0; i < ncpus; ++i) { + fiber_control::affinity_type affinity; + affinity.clear(); affinity.set_bit(i); + boost::function invoke = boost::bind(member_fun, this, i); + threads.launch(boost::bind( + &powerlyra_sync_engine::thread_launch_wrapped_event_counter, + this, + invoke), affinity); + } + // Wait for all threads to finish + threads.join(); + rmi.barrier(); + if (ncpus <= 1) { + DECREMENT_EVENT(EVENT_ACTIVE_CPUS, 1); + } + } // end of run_synchronous + + inline bool high_master_lvid(const lvid_type lvid); + inline bool low_master_lvid(const lvid_type lvid); + inline bool high_mirror_lvid(const lvid_type lvid); + inline bool low_mirror_lvid(const lvid_type lvid); + + // /** + // * \brief Initialize all vertex programs by invoking + // * \ref graphlab::ivertex_program::init on all vertices. + // * + // * @param thread_id the thread to run this as which determines + // * which vertices to process. + // */ + // void initialize_vertex_programs(size_t thread_id); + + /** + * \brief Synchronize all message data. + * + * @param thread_id the thread to run this as which determines + * which vertices to process. + */ + void exchange_messages(size_t thread_id); + + + /** + * \brief Invoke the \ref graphlab::ivertex_program::init function + * on all vertex programs that have inbound messages. + * + * @param thread_id the thread to run this as which determines + * which vertices to process. + */ + void receive_messages(size_t thread_id); + + + /** + * \brief Execute the \ref graphlab::ivertex_program::gather function on all + * vertices that received messages for the edges specified by the + * \ref graphlab::ivertex_program::gather_edges. + * + * @param thread_id the thread to run this as which determines + * which vertices to process. + */ + void execute_gathers(size_t thread_id); + + + + + /** + * \brief Execute the \ref graphlab::ivertex_program::apply function on all + * all vertices that received messages in this super-step (active). + * + * @param thread_id the thread to run this as which determines + * which vertices to process. + */ + void execute_applys(size_t thread_id); + + /** + * \brief Execute the \ref graphlab::ivertex_program::scatter function on all + * vertices that received messages for the edges specified by the + * \ref graphlab::ivertex_program::scatter_edges. + * + * @param thread_id the thread to run this as which determines + * which vertices to process. + */ + void execute_scatters(size_t thread_id); + + // Data Synchronization =================================================== + /** + * \brief Send the activation messages (vertex program and edge set) + * for the local vertex id to all of its mirrors. + * + * @param [in] lvid the vertex to sync. It must be the master of that vertex. + */ + void send_activs(lvid_type lvid, size_t thread_id); + + /** + * \brief do activation to local mirros. + * + * This function is a callback of express, and will be invoked when receives + * activation message. + */ + void recv_activs(); + + /** + * \brief Send the update messages (vertex data, program and edge set) + * for the local vertex id to all of its mirrors. + * + * @param [in] lvid the vertex to sync. It must be the master of that vertex. + */ + void send_updates(lvid_type lvid, size_t thread_id); + + /** + * \brief do update to local mirros. + * + * This function is a callback of express, and will be invoked when receives + * update message. + */ + void recv_updates(); + + /** + * \brief Send the gather accum for the vertex id to its master. + * + * @param [in] lvid the vertex to send the gather value to + * @param [in] accum the locally computed gather value. + */ + void send_accum(lvid_type lvid, const gather_type& accum, + const size_t thread_id); + + + /** + * \brief Receive the gather accums from the buffered exchange. + * + * This function returns when there is nothing left in the + * buffered exchange and should be called after the buffered + * exchange has been flushed + */ + void recv_accums(); + + /** + * \brief Send the scatter messages for the vertex id to its master. + * + * @param [in] lvid the vertex to send + */ + void send_message(lvid_type lvid, const message_type& message, + const size_t thread_id); + + /** + * \brief Receive the scatter messages from the buffered exchange. + * + * This function returns when there is nothing left in the + * buffered exchange and should be called after the buffered + * exchange has been flushed + */ + void recv_messages(); + + + }; // end of class powerlyra_sync_engine + + + + + + + + + + + + + + + + + + + + + + + + + + /** + * Constructs an synchronous distributed engine. + * The number of threads to create are read from + * opts::get_ncpus(). + * + * Valid engine options (graphlab_options::get_engine_args()): + * \arg \c max_iterations Sets the maximum number of iterations the + * engine will run for. + * \arg \c use_cache If set to true, partial gathers are cached. + * See \ref gather_caching to understand the behavior of the + * gather caching model and how it may be used to accelerate program + * performance. + * + * \param dc Distributed controller to associate with + * \param graph The graph to schedule over. The graph must be fully + * constructed and finalized. + * \param opts A graphlab_options object containing options and parameters + * for the engine. + */ + template + powerlyra_sync_engine:: + powerlyra_sync_engine(distributed_control& dc, + graph_type& graph, + const graphlab_options& opts) : + rmi(dc, this), graph(graph), + ncpus(opts.get_ncpus()), + threads(2*1024*1024 /* 2MB stack per fiber*/), + thread_barrier(opts.get_ncpus()), + max_iterations(-1), snapshot_interval(-1), iteration_counter(0), + print_interval(5), timeout(0), sched_allv(false), + activ_exchange(dc), + update_exchange(dc), + accum_exchange(dc), + message_exchange(dc), + aggregator(dc, graph, new context_type(*this, graph)) { + // Process any additional options + std::vector keys = opts.get_engine_args().get_option_keys(); + per_thread_compute_time.resize(opts.get_ncpus()); + use_cache = false; + foreach(std::string opt, keys) { + if (opt == "max_iterations") { + opts.get_engine_args().get_option("max_iterations", max_iterations); + if (rmi.procid() == 0) + logstream(LOG_EMPH) << "Engine Option: max_iterations = " + << max_iterations << std::endl; + } else if (opt == "timeout") { + opts.get_engine_args().get_option("timeout", timeout); + if (rmi.procid() == 0) + logstream(LOG_EMPH) << "Engine Option: timeout = " + << timeout << std::endl; + } else if (opt == "use_cache") { + opts.get_engine_args().get_option("use_cache", use_cache); + if (rmi.procid() == 0) + logstream(LOG_EMPH) << "Engine Option: use_cache = " + << use_cache << std::endl; + } else if (opt == "snapshot_interval") { + opts.get_engine_args().get_option("snapshot_interval", snapshot_interval); + if (rmi.procid() == 0) + logstream(LOG_EMPH) << "Engine Option: snapshot_interval = " + << snapshot_interval << std::endl; + } else if (opt == "snapshot_path") { + opts.get_engine_args().get_option("snapshot_path", snapshot_path); + if (rmi.procid() == 0) + logstream(LOG_EMPH) << "Engine Option: snapshot_path = " + << snapshot_path << std::endl; + } else if (opt == "sched_allv") { + opts.get_engine_args().get_option("sched_allv", sched_allv); + if (rmi.procid() == 0) + logstream(LOG_EMPH) << "Engine Option: sched_allv = " + << sched_allv << std::endl; + } else { + logstream(LOG_FATAL) << "Unexpected Engine Option: " << opt << std::endl; + } + } + + if (snapshot_interval >= 0 && snapshot_path.length() == 0) { + logstream(LOG_FATAL) + << "Snapshot interval specified, but no snapshot path" << std::endl; + } + INITIALIZE_EVENT_LOG(dc); + ADD_CUMULATIVE_EVENT(EVENT_APPLIES, "Applies", "Calls"); + ADD_CUMULATIVE_EVENT(EVENT_GATHERS , "Gathers", "Calls"); + ADD_CUMULATIVE_EVENT(EVENT_SCATTERS , "Scatters", "Calls"); + ADD_INSTANTANEOUS_EVENT(EVENT_ACTIVE_CPUS, "Active Threads", "Threads"); + + // Graph should has been finalized + ASSERT_TRUE(graph.is_finalized()); + // Only support zone cuts + ASSERT_TRUE(graph.get_cuts_type() == graph_type::HYBRID_CUTS || graph.get_cuts_type() == graph_type::HYBRID_GINGER_CUTS); + // if (rmi.procid() == 0) graph.dump_graph_info(); + + init(); + } // end of powerlyra_sync_engine + + + template + void powerlyra_sync_engine:: init() { + memory_info::log_usage("Before Engine Initialization"); + + resize(); + + // Clear up + force_abort = false; + iteration_counter = 0; + completed_gathers = 0; + completed_applys = 0; + completed_scatters = 0; + has_message.clear(); + has_gather_accum.clear(); + has_cache.clear(); + active_superstep.clear(); + active_minorstep.clear(); + + memory_info::log_usage("After Engine Initialization"); + } + + + template + void powerlyra_sync_engine:: resize() { + size_t l_nverts = graph.num_local_vertices(); + + // Allocate vertex locks and vertex programs + vlocks.resize(l_nverts); + vertex_programs.resize(l_nverts); + edge_dirs.resize(l_nverts); + + // Allocate messages and message bitset + messages.resize(l_nverts, message_type()); + has_message.resize(l_nverts); + + // Allocate gather accumulators and accumulator bitset + gather_accum.resize(l_nverts, gather_type()); + has_gather_accum.resize(l_nverts); + + // If caching is used then allocate cache data-structures + if (use_cache) { + gather_cache.resize(l_nverts, gather_type()); + has_cache.resize(l_nverts); + } + // Allocate bitset to track active vertices on each bitset. + active_superstep.resize(l_nverts); + active_minorstep.resize(l_nverts); + } + + + template + typename powerlyra_sync_engine::aggregator_type* + powerlyra_sync_engine::get_aggregator() { + return &aggregator; + } // end of get_aggregator + + + template + void powerlyra_sync_engine::internal_stop() { + for (size_t i = 0; i < rmi.numprocs(); ++i) + rmi.remote_call(i, &engine_type::rpc_stop); + } // end of internal_stop + + + template + void powerlyra_sync_engine::rpc_stop() { + force_abort = true; + } // end of rpc_stop + + + template + void powerlyra_sync_engine:: + signal(vertex_id_type gvid, const message_type& message) { + if (vlocks.size() != graph.num_local_vertices()) + resize(); + rmi.barrier(); + internal_signal_rpc(gvid, message); + rmi.barrier(); + } // end of signal + + + + template + void powerlyra_sync_engine:: + signal_all(const message_type& message, const std::string& order) { + if (vlocks.size() != graph.num_local_vertices()) + resize(); + for(lvid_type lvid = 0; lvid < graph.num_local_vertices(); ++lvid) { + if(graph.l_is_master(lvid)) { + internal_signal(vertex_type(graph.l_vertex(lvid)), message); + } + } + } // end of signal all + + + template + void powerlyra_sync_engine:: + signal_vset(const vertex_set& vset, + const message_type& message, const std::string& order) { + if (vlocks.size() != graph.num_local_vertices()) + resize(); + for(lvid_type lvid = 0; lvid < graph.num_local_vertices(); ++lvid) { + if(graph.l_is_master(lvid) && vset.l_contains(lvid)) { + internal_signal(vertex_type(graph.l_vertex(lvid)), message); + } + } + } // end of signal all + + + template + void powerlyra_sync_engine:: + internal_signal(const vertex_type& vertex, + const message_type& message) { + const lvid_type lvid = vertex.local_id(); + vlocks[lvid].lock(); + if( has_message.get(lvid) ) { + messages[lvid] += message; + } else { + messages[lvid] = message; + has_message.set_bit(lvid); + } + vlocks[lvid].unlock(); + } // end of internal_signal + + template + void powerlyra_sync_engine:: + internal_signal(const vertex_type& vertex) { + const lvid_type lvid = vertex.local_id(); + messages[lvid] = message_type(); + has_message.set_bit(lvid); + } // end of internal_signal + + template + void powerlyra_sync_engine:: + internal_signal_gvid(vertex_id_type gvid, const message_type& message) { + procid_t proc = graph.master(gvid); + if(proc == rmi.procid()) internal_signal_rpc(gvid, message); + else rmi.remote_call(proc, + &powerlyra_sync_engine::internal_signal_rpc, + gvid, message); + } // end of internal_signal_gvid + + template + void powerlyra_sync_engine:: + internal_signal_rpc(vertex_id_type gvid, + const message_type& message) { + if (graph.is_master(gvid)) { + internal_signal(graph.vertex(gvid), message); + } + } // end of internal_signal_rpc + + + + + + template + void powerlyra_sync_engine:: + internal_post_delta(const vertex_type& vertex, const gather_type& delta) { + const bool caching_enabled = !gather_cache.empty(); + if(caching_enabled) { + const lvid_type lvid = vertex.local_id(); + vlocks[lvid].lock(); + if( has_cache.get(lvid) ) { + gather_cache[lvid] += delta; + } else { + // You cannot add a delta to an empty cache. A complete + // gather must have been run. + // gather_cache[lvid] = delta; + // has_cache.set_bit(lvid); + } + vlocks[lvid].unlock(); + } + } // end of post_delta + + + template + void powerlyra_sync_engine:: + internal_clear_gather_cache(const vertex_type& vertex) { + const bool caching_enabled = !gather_cache.empty(); + const lvid_type lvid = vertex.local_id(); + if(caching_enabled && has_cache.get(lvid)) { + vlocks[lvid].lock(); + gather_cache[lvid] = gather_type(); + has_cache.clear_bit(lvid); + vlocks[lvid].unlock(); + } + } // end of clear_gather_cache + + + + + template + size_t powerlyra_sync_engine:: + num_updates() const { return completed_applys.value; } + + template + float powerlyra_sync_engine:: + elapsed_seconds() const { return timer::approx_time_seconds() - start_time; } + + template + double powerlyra_sync_engine:: + execution_time() const { return exec_time; } + + template + int powerlyra_sync_engine:: + iteration() const { return iteration_counter; } + + + + template + size_t powerlyra_sync_engine::total_memory_usage() const { + size_t allocated_memory = memory_info::allocated_bytes(); + rmi.all_reduce(allocated_memory); + return allocated_memory; + } // compute the total memory usage of the GraphLab system + + + template + execution_status::status_enum powerlyra_sync_engine:: + start() { + if (vlocks.size() != graph.num_local_vertices()) + resize(); + completed_gathers = 0; + completed_applys = 0; + completed_scatters = 0; + rmi.barrier(); + + // Initialization code ================================================== + // Reset event log counters? + // Start the timer + start_time = timer::approx_time_seconds(); + exec_time = exch_time = recv_time = + gather_time = apply_time = scatter_time = 0.0; + graphlab::timer ti, bk_ti, iter_ti; + iteration_counter = 0; + force_abort = false; + execution_status::status_enum termination_reason = execution_status::UNSET; + aggregator.start(); + rmi.barrier(); + + if (snapshot_interval == 0) { + graph.save_binary(snapshot_path); + } + + float last_print = -print_interval; // print the first iteration + if (rmi.procid() == 0) { + logstream(LOG_EMPH) << "Iteration counter will only output every " + << print_interval << " seconds." + << std::endl; + } + + // Program Main loop ==================================================== + ti.start(); + while(iteration_counter < max_iterations && !force_abort ) { + + // Check first to see if we are out of time + if(timeout != 0 && timeout < elapsed_seconds()) { + termination_reason = execution_status::TIMEOUT; + break; + } + + bool print_this_round = (elapsed_seconds() - last_print) >= print_interval; + //debug + //print_this_round = true; + if(rmi.procid() == 0 && print_this_round) { + logstream(LOG_DEBUG) + << rmi.procid() << ": Starting iteration: " << iteration_counter + << std::endl; + last_print = elapsed_seconds(); + } + // Reset Active vertices ---------------------------------------------- + // Clear the active super-step and minor-step bits which will + // be set upon receiving messages + active_superstep.clear(); active_minorstep.clear(); + has_gather_accum.clear(); + num_active_vertices = 0; + rmi.barrier(); + + + // Exchange Messages -------------------------------------------------- + // Powergraph: send messages from replicas to master + // - set messages and has_message + // Lyra: none + // + // if (rmi.procid() == 0) std::cout << "Exchange messages..." << std::endl; + bk_ti.start(); + run_synchronous( &powerlyra_sync_engine::exchange_messages ); + exch_time += bk_ti.current_time(); + /** + * Post conditions: + * 1) master (high and low) vertices have messages + */ + + // Receive Messages --------------------------------------------------- + // 1. calculate the number of active vertices + // 2. call init and gather_edges + // 3. set active_superstep, active_minorstep and edge_dirs + // 4. clear has_message + // Powergraph: send vprog and edge_dirs from master to replicas + // - set vprog, edge_dirs and set active_minorstep + // Lyra: none + // + // if (rmi.procid() == 0) std::cout << "Receive messages..." << std::endl; + bk_ti.start(); + run_synchronous( &powerlyra_sync_engine::receive_messages ); + if (sched_allv) active_minorstep.fill(); + has_message.clear(); + recv_time += bk_ti.current_time(); + /** + * Post conditions: + * 1) there are no messages remaining + * 2) All masters that received messages have their + * active_superstep bit set + * 3) All masters and mirrors that are to participate in the + * next gather phases have their active_minorstep bit + * set. + * 4) num_active_vertices is the number of vertices that + * received messages. + */ + + // Check termination condition --------------------------------------- + size_t total_active_vertices = num_active_vertices; + rmi.all_reduce(total_active_vertices); + if (rmi.procid() == 0 /*&& print_this_round*/) { + logstream(LOG_EMPH) + << iteration_counter << ":" + << "\tactive vertices = " << total_active_vertices + << "\ttime = " << iter_ti.current_time() + << "\tmsg = " << rmi.dc().network_bytes_sent() + << std::endl; + } + if(total_active_vertices == 0 ) { + termination_reason = execution_status::TASK_DEPLETION; + break; + } + iter_ti.start(); + + // Execute gather operations------------------------------------------- + // 1. call pre_local_gather, gather and post_local_gather + // 2. (master) set gather_accum and has_gather_accum + // 3. clear active_minorstep + // Powergraph: send gather_accum from replicas to master + // - set gather_accum and has_gather_accum + // Lyra: none + // + // if (rmi.procid() == 0) std::cout << "Gathering..." << std::endl; + bk_ti.start(); + run_synchronous( &powerlyra_sync_engine::execute_gathers ); + // Clear the minor step bit since only super-step vertices + // (only master vertices are required to participate in the + // apply step) + active_minorstep.clear(); + gather_time += bk_ti.current_time(); + /** + * Post conditions: + * 1) gather_accum for all master vertices contains the + * result of all the gathers (even if they are drawn from + * cache) + * 2) No minor-step bits are set + */ + + // Execute Apply Operations ------------------------------------------- + // 1. call apply and scatter_edges + // 2. set edge_dirs and active_minorstep + // 3. send vdata, vprog and edge_dirs from master to replicas + // - set vdata, vprog, edge_dirs and active_minorstep + // + // if (rmi.procid() == 0) std::cout << "Applying..." << std::endl; + bk_ti.start(); + run_synchronous( &powerlyra_sync_engine::execute_applys ); + apply_time += bk_ti.current_time(); + /** + * Post conditions: + * 1) any changes to the vertex data have been synchronized + * with all mirrors. + * 2) all gather accumulators have been cleared + * 3) If a vertex program is participating in the scatter + * phase its minor-step bit has been set to active (both + * masters and mirrors) and the vertex program has been + * synchronized with the mirrors. + */ + + + // Execute Scatter Operations ----------------------------------------- + // 1. call scatter (signal: set messages and has_message) + // + // if (rmi.procid() == 0) std::cout << "Scattering..." << std::endl; + bk_ti.start(); + run_synchronous( &powerlyra_sync_engine::execute_scatters ); + scatter_time += bk_ti.current_time(); + /** + * Post conditions: + * 1) NONE + */ + if(rmi.procid() == 0 && print_this_round) + logstream(LOG_DEBUG) << "\t Running Aggregators" << std::endl; + // probe the aggregator + aggregator.tick_synchronous(); + + ++iteration_counter; + + if (snapshot_interval > 0 && iteration_counter % snapshot_interval == 0) { + graph.save_binary(snapshot_path); + } + } + exec_time = ti.current_time(); + + if (rmi.procid() == 0) { + logstream(LOG_EMPH) << iteration_counter + << " iterations completed." << std::endl; + memory_info::log_usage("Execution completed."); + } + // Final barrier to ensure that all engines terminate at the same time + double total_compute_time = 0; + for (size_t i = 0;i < per_thread_compute_time.size(); ++i) { + total_compute_time += per_thread_compute_time[i]; + } + std::vector all_compute_time_vec(rmi.numprocs()); + all_compute_time_vec[rmi.procid()] = total_compute_time; + rmi.all_gather(all_compute_time_vec); + + logstream(LOG_INFO) << "Local Calls(G|A|S): " + << completed_gathers.value << "|" + << completed_applys.value << "|" + << completed_scatters.value + << std::endl; + + size_t global_completed = completed_applys; + rmi.all_reduce(global_completed); + completed_applys = global_completed; + + global_completed = completed_gathers; + rmi.all_reduce(global_completed); + completed_gathers = global_completed; + + global_completed = completed_scatters; + rmi.all_reduce(global_completed); + completed_scatters = global_completed; + + if (rmi.procid() == 0) { + logstream(LOG_INFO) << "Total Calls(G|A|S): " + << completed_gathers.value << "|" + << completed_applys.value << "|" + << completed_scatters.value + << std::endl; + logstream(LOG_INFO) << "Compute Balance: "; + for (size_t i = 0;i < all_compute_time_vec.size(); ++i) { + logstream(LOG_INFO) << all_compute_time_vec[i] << " "; + } + logstream(LOG_INFO) << std::endl; + logstream(LOG_EMPH) << " Execution Time: " << exec_time << std::endl; + logstream(LOG_EMPH) << "Breakdown(X|R|G|A|S): " + << exch_time << "|" + << recv_time << "|" + << gather_time << "|" + << apply_time << "|" + << scatter_time + << std::endl; + } + rmi.full_barrier(); + // Stop the aggregator + aggregator.stop(); + // return the final reason for termination + return termination_reason; + } // end of start + + template + inline bool powerlyra_sync_engine:: + high_master_lvid(const lvid_type lvid) { + return graph.l_type(lvid) == graph_type::HIGH_MASTER; + } + + template + inline bool powerlyra_sync_engine:: + low_master_lvid(const lvid_type lvid) { + return graph.l_type(lvid) == graph_type::LOW_MASTER; + } + + template + inline bool powerlyra_sync_engine:: + high_mirror_lvid(const lvid_type lvid) { + return graph.l_type(lvid) == graph_type::HIGH_MIRROR; + } + + template + inline bool powerlyra_sync_engine:: + low_mirror_lvid(const lvid_type lvid) { + return graph.l_type(lvid) == graph_type::LOW_MIRROR; + } + + template + void powerlyra_sync_engine:: + exchange_messages(const size_t thread_id) { + context_type context(*this, graph); + fixed_dense_bitset<8 * sizeof(size_t)> local_bitset; // a word-size = 64 bit + + while (1) { + // increment by a word at a time + lvid_type lvid_block_start = + shared_lvid_counter.inc_ret_last(8 * sizeof(size_t)); + if (lvid_block_start >= graph.num_local_vertices()) break; + // get the bit field from has_message + size_t lvid_bit_block = has_message.containing_word(lvid_block_start); + if (lvid_bit_block == 0) continue; + // initialize a word sized bitfield + local_bitset.clear(); + local_bitset.initialize_from_mem(&lvid_bit_block, sizeof(size_t)); + foreach(size_t lvid_block_offset, local_bitset) { + lvid_type lvid = lvid_block_start + lvid_block_offset; + if (lvid >= graph.num_local_vertices()) break; + + // [TARGET]: High/Low-degree Mirrors + if(high_mirror_lvid(lvid) + || (low_mirror_lvid(lvid) // only if scatter via in-edge + && ((edge_dirs[lvid] == graphlab::IN_EDGES) + || (edge_dirs[lvid] == graphlab::ALL_EDGES)))) { + send_message(lvid, messages[lvid], thread_id); + has_message.clear_bit(lvid); + // clear the message to save memory + messages[lvid] = message_type(); + } + } + } // end of loop over vertices to send messages + message_exchange.partial_flush(); + thread_barrier.wait(); + if(thread_id == 0) message_exchange.flush(); + thread_barrier.wait(); + recv_messages(); + } // end of exchange_messages + + + template + void powerlyra_sync_engine:: + receive_messages(const size_t thread_id) { + context_type context(*this, graph); + fixed_dense_bitset<8 * sizeof(size_t)> local_bitset; // a word-size = 64 bit + size_t nactive_inc = 0; + + while (1) { + // increment by a word at a time + lvid_type lvid_block_start = + shared_lvid_counter.inc_ret_last(8 * sizeof(size_t)); + if (lvid_block_start >= graph.num_local_vertices()) break; + // get the bit field from has_message + size_t lvid_bit_block = has_message.containing_word(lvid_block_start); + if (lvid_bit_block == 0) continue; + // initialize a word sized bitfield + local_bitset.clear(); + local_bitset.initialize_from_mem(&lvid_bit_block, sizeof(size_t)); + foreach(size_t lvid_block_offset, local_bitset) { + lvid_type lvid = lvid_block_start + lvid_block_offset; + if (lvid >= graph.num_local_vertices()) break; + + // [TARGET]: High/Low-degree Masters + ASSERT_TRUE(graph.l_is_master(lvid)); + // The vertex becomes active for this superstep + active_superstep.set_bit(lvid); + ++nactive_inc; + // Pass the message to the vertex program + const vertex_type vertex(graph.l_vertex(lvid)); + vertex_programs[lvid].init(context, vertex, messages[lvid]); + // clear the message to save memory + messages[lvid] = message_type(); + if (sched_allv) continue; + // Determine if the gather should be run + const vertex_program_type& const_vprog = vertex_programs[lvid]; + edge_dirs[lvid] = const_vprog.gather_edges(context, vertex); + + if(edge_dirs[lvid] != graphlab::NO_EDGES) { + active_minorstep.set_bit(lvid); + // send Gx1 msgs + if (high_master_lvid(lvid) + || (low_master_lvid(lvid) // only if gather via out-edge + && ((edge_dirs[lvid] == graphlab::OUT_EDGES) + || (edge_dirs[lvid] == graphlab::ALL_EDGES)))) { + send_activs(lvid, thread_id); + } + } + } + } + num_active_vertices += nactive_inc; + activ_exchange.partial_flush(); + thread_barrier.wait(); + // Flush the buffer and finish receiving any remaining activations. + if(thread_id == 0) activ_exchange.flush(); // call full_barrier + thread_barrier.wait(); + recv_activs(); + } // end of receive_messages + + + template + void powerlyra_sync_engine:: + execute_gathers(const size_t thread_id) { + context_type context(*this, graph); + const bool caching_enabled = !gather_cache.empty(); + fixed_dense_bitset<8 * sizeof(size_t)> local_bitset; // a word-size = 64 bit + size_t ngather_inc = 0; + timer ti; + + while (1) { + // increment by a word at a time + lvid_type lvid_block_start = + shared_lvid_counter.inc_ret_last(8 * sizeof(size_t)); + if (lvid_block_start >= graph.num_local_vertices()) break; + // get the bit field from has_message + size_t lvid_bit_block = active_minorstep.containing_word(lvid_block_start); + if (lvid_bit_block == 0) continue; + // initialize a word sized bitfield + local_bitset.clear(); + local_bitset.initialize_from_mem(&lvid_bit_block, sizeof(size_t)); + foreach(size_t lvid_block_offset, local_bitset) { + lvid_type lvid = lvid_block_start + lvid_block_offset; + if (lvid >= graph.num_local_vertices()) break; + + // [TARGET]: High/Low-degree Masters, and High/Low-degree Mirrors + bool accum_is_set = false; + gather_type accum = gather_type(); + // if caching is enabled and we have a cache entry then use + // that as the accum + if (caching_enabled && has_cache.get(lvid)) { + accum = gather_cache[lvid]; + accum_is_set = true; + } else { + // recompute the local contribution to the gather + const vertex_program_type& vprog = vertex_programs[lvid]; + local_vertex_type local_vertex = graph.l_vertex(lvid); + const vertex_type vertex(local_vertex); + const edge_dir_type gather_dir = edge_dirs[lvid]; + + size_t edges_touched = 0; + vprog.pre_local_gather(accum); + // Loop over in edges + if (gather_dir == IN_EDGES || gather_dir == ALL_EDGES) { + foreach(local_edge_type local_edge, local_vertex.in_edges()) { + edge_type edge(local_edge); + if(accum_is_set) { // \todo hint likely + accum += vprog.gather(context, vertex, edge); + } else { + accum = vprog.gather(context, vertex, edge); + accum_is_set = true; + } + ++edges_touched; + } + } // end of if in_edges/all_edges + // Loop over out edges + if(gather_dir == OUT_EDGES || gather_dir == ALL_EDGES) { + foreach(local_edge_type local_edge, local_vertex.out_edges()) { + edge_type edge(local_edge); + if(accum_is_set) { // \todo hint likely + accum += vprog.gather(context, vertex, edge); + } else { + accum = vprog.gather(context, vertex, edge); + accum_is_set = true; + } + ++edges_touched; + } + } // end of if out_edges/all_edges + INCREMENT_EVENT(EVENT_GATHERS, edges_touched); + ++ngather_inc; + vprog.post_local_gather(accum); + + // If caching is enabled then save the accumulator to the + // cache for future iterations. Note that it is possible + // that the accumulator was never set in which case we are + // effectively "zeroing out" the cache. + if(caching_enabled && accum_is_set) { + gather_cache[lvid] = accum; has_cache.set_bit(lvid); + } // end of if caching enabled + } + + // If the accum contains a value for the gather + if (accum_is_set) send_accum(lvid, accum, thread_id); + } + } // end of loop over vertices to compute gather accumulators + completed_gathers += ngather_inc; + accum_exchange.partial_flush(); + thread_barrier.wait(); + per_thread_compute_time[thread_id] += ti.current_time(); + if(thread_id == 0) accum_exchange.flush(); // full_barrier + thread_barrier.wait(); + recv_accums(); + } // end of execute_gathers + + + template + void powerlyra_sync_engine:: + execute_applys(const size_t thread_id) { + context_type context(*this, graph); + fixed_dense_bitset<8 * sizeof(size_t)> local_bitset; // allocate a word size = 64bits + size_t napply_inc = 0; + timer ti; + + while (1) { + // increment by a word at a time + lvid_type lvid_block_start = + shared_lvid_counter.inc_ret_last(8 * sizeof(size_t)); + if (lvid_block_start >= graph.num_local_vertices()) break; + // get the bit field from has_message + size_t lvid_bit_block = active_superstep.containing_word(lvid_block_start); + if (lvid_bit_block == 0) continue; + // initialize a word sized bitfield + local_bitset.clear(); + local_bitset.initialize_from_mem(&lvid_bit_block, sizeof(size_t)); + foreach(size_t lvid_block_offset, local_bitset) { + lvid_type lvid = lvid_block_start + lvid_block_offset; + if (lvid >= graph.num_local_vertices()) break; + + // [TARGET]: High/Low-degree Masters + // Only master vertices can be active in a super-step + ASSERT_TRUE(graph.l_is_master(lvid)); + vertex_type vertex(graph.l_vertex(lvid)); + // Get the local accumulator. Note that it is possible that + // the gather_accum was not set during the gather. + const gather_type& accum = gather_accum[lvid]; + INCREMENT_EVENT(EVENT_APPLIES, 1); + vertex_programs[lvid].apply(context, vertex, accum); + // record an apply as a completed task + ++napply_inc; + // clear the accumulator to save some memory + gather_accum[lvid] = gather_type(); + // determine if a scatter operation is needed + const vertex_program_type& const_vprog = vertex_programs[lvid]; + const vertex_type const_vertex = vertex; + edge_dirs[lvid] = const_vprog.scatter_edges(context, const_vertex); + + if (edge_dirs[lvid] != graphlab::NO_EDGES) + active_minorstep.set_bit(lvid); + // send Ax1 and Sx1 msgs + send_updates(lvid, thread_id); + } + } // end of loop over vertices to run apply + completed_applys += napply_inc; + update_exchange.partial_flush(); + thread_barrier.wait(); + per_thread_compute_time[thread_id] += ti.current_time(); + // Flush the buffer and finish receiving any remaining updates. + if(thread_id == 0) update_exchange.flush(); // full_barrier + thread_barrier.wait(); + recv_updates(); + } // end of execute_applys + + + template + void powerlyra_sync_engine:: + execute_scatters(const size_t thread_id) { + context_type context(*this, graph); + fixed_dense_bitset<8 * sizeof(size_t)> local_bitset; // allocate a word size = 64 bits + size_t nscatter_inc = 0; + timer ti; + + while (1) { + // increment by a word at a time + lvid_type lvid_block_start = + shared_lvid_counter.inc_ret_last(8 * sizeof(size_t)); + if (lvid_block_start >= graph.num_local_vertices()) break; + // get the bit field from has_message + size_t lvid_bit_block = active_minorstep.containing_word(lvid_block_start); + if (lvid_bit_block == 0) continue; + // initialize a word sized bitfield + local_bitset.clear(); + local_bitset.initialize_from_mem(&lvid_bit_block, sizeof(size_t)); + foreach(size_t lvid_block_offset, local_bitset) { + lvid_type lvid = lvid_block_start + lvid_block_offset; + if (lvid >= graph.num_local_vertices()) break; + + // [TARGET]: High/Low-degree Masters, and High/Low-degree Mirrors + const vertex_program_type& vprog = vertex_programs[lvid]; + local_vertex_type local_vertex = graph.l_vertex(lvid); + const vertex_type vertex(local_vertex); + const edge_dir_type scatter_dir = edge_dirs[lvid]; + + size_t edges_touched = 0; + // Loop over in edges + if(scatter_dir == IN_EDGES || scatter_dir == ALL_EDGES) { + foreach(local_edge_type local_edge, local_vertex.in_edges()) { + edge_type edge(local_edge); + vprog.scatter(context, vertex, edge); + ++edges_touched; + } + } // end of if in_edges/all_edges + // Loop over out edges + if(scatter_dir == OUT_EDGES || scatter_dir == ALL_EDGES) { + foreach(local_edge_type local_edge, local_vertex.out_edges()) { + edge_type edge(local_edge); + vprog.scatter(context, vertex, edge); + ++edges_touched; + } + } // end of if out_edges/all_edges + INCREMENT_EVENT(EVENT_SCATTERS, edges_touched); + ++nscatter_inc; + } // end of if active on this minor step + } // end of loop over vertices to complete scatter operation + completed_scatters += nscatter_inc; + per_thread_compute_time[thread_id] += ti.current_time(); + } // end of execute_scatters + + + + // Data Synchronization =================================================== + template + inline void powerlyra_sync_engine:: + send_activs(lvid_type lvid, const size_t thread_id) { + ASSERT_TRUE(graph.l_is_master(lvid)); + const vertex_id_type vid = graph.global_vid(lvid); + local_vertex_type vertex = graph.l_vertex(lvid); + foreach(const procid_t& mirror, vertex.mirrors()) { + activ_exchange.send(mirror, + make_triple(vid, + vertex_programs[lvid], + edge_dirs[lvid])); + } + } // end of send_activ + + template + inline void powerlyra_sync_engine:: + recv_activs() { + typename activ_exchange_type::recv_buffer_type recv_buffer; + while(activ_exchange.recv(recv_buffer)) { + for (size_t i = 0;i < recv_buffer.size(); ++i) { + typename activ_exchange_type::buffer_type& buffer = recv_buffer[i].buffer; + foreach(const vid_vprog_edir_triple_type& t, buffer) { + const lvid_type lvid = graph.local_vid(t.first); + ASSERT_FALSE(graph.l_is_master(lvid)); + vertex_programs[lvid] = t.second; + edge_dirs[lvid] = t.third; + active_minorstep.set_bit(lvid); + } + } + } + } // end of recv activs programs + + template + inline void powerlyra_sync_engine:: + send_updates(lvid_type lvid, const size_t thread_id) { + ASSERT_TRUE(graph.l_is_master(lvid)); + const vertex_id_type vid = graph.global_vid(lvid); + local_vertex_type vertex = graph.l_vertex(lvid); + foreach(const procid_t& mirror, vertex.mirrors()) { + update_exchange.send(mirror, + make_tetrad(vid, + vertex.data(), + vertex_programs[lvid], + edge_dirs[lvid])); + } + } // end of send_update + + template + inline void powerlyra_sync_engine:: + recv_updates() { + typename update_exchange_type::recv_buffer_type recv_buffer; + while(update_exchange.recv(recv_buffer)) { + for (size_t i = 0;i < recv_buffer.size(); ++i) { + typename update_exchange_type::buffer_type& buffer = recv_buffer[i].buffer; + foreach(const vid_vdata_vprog_edir_tetrad_type& t, buffer) { + const lvid_type lvid = graph.local_vid(t.first); + ASSERT_FALSE(graph.l_is_master(lvid)); + graph.l_vertex(lvid).data() = t.second; + if (t.fourth != graphlab::NO_EDGES) { + vertex_programs[lvid] = t.third; + edge_dirs[lvid] = t.fourth; + active_minorstep.set_bit(lvid); + } + } + } + } + } // end of recv_updates + + template + inline void powerlyra_sync_engine:: + send_accum(lvid_type lvid, const gather_type& accum, const size_t thread_id) { + if(graph.l_is_master(lvid)) { + vlocks[lvid].lock(); + if(has_gather_accum.get(lvid)) { + gather_accum[lvid] += accum; + } else { + gather_accum[lvid] = accum; + has_gather_accum.set_bit(lvid); + } + vlocks[lvid].unlock(); + } else { + const procid_t master = graph.l_master(lvid); + const vertex_id_type vid = graph.global_vid(lvid); + accum_exchange.send(master, std::make_pair(vid, accum)); + } + } // end of send_accum + + template + inline void powerlyra_sync_engine:: + recv_accums() { + typename accum_exchange_type::recv_buffer_type recv_buffer; + while(accum_exchange.recv(recv_buffer)) { + for (size_t i = 0;i < recv_buffer.size(); ++i) { + typename accum_exchange_type::buffer_type& buffer = recv_buffer[i].buffer; + foreach(const vid_gather_pair_type& pair, buffer) { + const lvid_type lvid = graph.local_vid(pair.first); + const gather_type& acc = pair.second; + ASSERT_TRUE(graph.l_is_master(lvid)); + vlocks[lvid].lock(); + if(has_gather_accum.get(lvid)) { + gather_accum[lvid] += acc; + } else { + gather_accum[lvid] = acc; + has_gather_accum.set_bit(lvid); + } + vlocks[lvid].unlock(); + } + } + } + } // end of recv_accums + + + template + inline void powerlyra_sync_engine:: + send_message(lvid_type lvid, const message_type& message, const size_t thread_id) { + ASSERT_FALSE(graph.l_is_master(lvid)); + const procid_t master = graph.l_master(lvid); + const vertex_id_type vid = graph.global_vid(lvid); + message_exchange.send(master, std::make_pair(vid, message)); + } // end of send_message + + template + inline void powerlyra_sync_engine:: + recv_messages() { + typename message_exchange_type::recv_buffer_type recv_buffer; + while(message_exchange.recv(recv_buffer)) { + for (size_t i = 0;i < recv_buffer.size(); ++i) { + typename message_exchange_type::buffer_type& buffer = recv_buffer[i].buffer; + foreach(const vid_message_pair_type& pair, buffer) { + const lvid_type lvid = graph.local_vid(pair.first); + const message_type& msg = pair.second; + ASSERT_TRUE(graph.l_is_master(lvid)); + vlocks[lvid].lock(); + if(has_message.get(lvid)) { + messages[lvid] += msg; + } else { + messages[lvid] = msg; + has_message.set_bit(lvid); + } + vlocks[lvid].unlock(); + } + } + } + } // end of recv_messages + +}; // namespace + + +#include + +#endif + diff --git a/src/graphlab/graph/builtin_parsers.hpp b/src/graphlab/graph/builtin_parsers.hpp index 64d34eef2e..e1d9e85bfb 100644 --- a/src/graphlab/graph/builtin_parsers.hpp +++ b/src/graphlab/graph/builtin_parsers.hpp @@ -179,6 +179,74 @@ namespace graphlab { } // end of adj parser #endif +#if defined(__cplusplus) && __cplusplus >= 201103L + // The spirit parser seems to have issues when compiling under + // C++11. Temporary workaround with a hard coded parser. TOFIX + template + bool radj_parser(Graph& graph, const std::string& srcfilename, + const std::string& line) { + // If the line is empty simply skip it + if(line.empty()) return true; + std::stringstream strm(line); + vertex_id_type target; + size_t n; + strm >> target; + if (strm.fail()) return false; + strm >> n; + if (strm.fail()) return true; + + std::vector sources; + while (strm.good()) { + vertex_id_type source; + strm >> source; + if (strm.fail()) break; + if (target != source) sources.push_back(source); + } + std::vector edatas; + edatas.resize(sources.size()); + graph.add_edge(sources, target, edatas); + if (n != sources.size()) return false; + return true; + } // end of radj parser + +#else + + template + bool radj_parser(Graph& graph, const std::string& srcfilename, + const std::string& line) { + // If the line is empty simply skip it + if(line.empty()) return true; + // We use the boost spirit parser which requires (too) many separate + // namespaces so to make things clear we shorten them here. + namespace qi = boost::spirit::qi; + namespace ascii = boost::spirit::ascii; + namespace phoenix = boost::phoenix; + vertex_id_type target(-1); + vertex_id_type nsources(-1); + std::vector sources; + const bool success = qi::phrase_parse + (line.begin(), line.end(), + // Begin grammar + ( + qi::ulong_[phoenix::ref(target) = qi::_1] >> -qi::char_(",") >> + qi::ulong_[phoenix::ref(nsources) = qi::_1] >> -qi::char_(",") >> + *(qi::ulong_[phoenix::push_back(phoenix::ref(sources), qi::_1)] % -qi::char_(",")) + ) + , + // End grammar + ascii::space); + // Test to see if the boost parser was able to parse the line + if(!success || nsources != sources.size()) { + logstream(LOG_ERROR) << "Parse error in vertex prior parser." << std::endl; + return false; + } + std::vector edatas; + edatas.resize(sources.size()); + graph.add_edge(sources, target, edatas); + return true; + } // end of radj parser +#endif + template struct tsv_writer{ typedef typename Graph::vertex_type vertex_type; @@ -189,8 +257,6 @@ namespace graphlab { } }; - - template struct graphjrl_writer{ diff --git a/src/graphlab/graph/distributed_graph.hpp b/src/graphlab/graph/distributed_graph.hpp index 4b62ed9849..e7936025a5 100644 --- a/src/graphlab/graph/distributed_graph.hpp +++ b/src/graphlab/graph/distributed_graph.hpp @@ -82,6 +82,9 @@ #include #include +#include +#include + #include #include @@ -401,11 +404,19 @@ namespace graphlab { friend class distributed_identity_ingress; friend class distributed_oblivious_ingress; friend class distributed_constrained_random_ingress; + friend class distributed_hybrid_ingress; + friend class distributed_hybrid_ginger_ingress; typedef graphlab::vertex_id_type vertex_id_type; typedef graphlab::lvid_type lvid_type; typedef graphlab::edge_id_type edge_id_type; + enum zone_type {HIGH_MASTER = 0, LOW_MASTER, + HIGH_MIRROR, LOW_MIRROR, NUM_ZONE_TYPES}; + + enum cuts_type {VERTEX_CUTS = 0, EDGE_CUTS, HYBRID_CUTS, HYBRID_GINGER_CUTS, + ZONE_CUTS, NUM_CUTS_TYPES}; + struct vertex_type; typedef bool edge_list_type; class edge_type; @@ -613,7 +624,7 @@ namespace graphlab { const graphlab_options& opts = graphlab_options()) : rpc(dc, this), finalized(false), vid2lvid(), nverts(0), nedges(0), local_own_nverts(0), nreplicas(0), - ingress_ptr(NULL), + how_cuts(VERTEX_CUTS), ingress_ptr(NULL), #ifdef _OPENMP vertex_exchange(dc, omp_get_max_threads()), #else @@ -637,6 +648,13 @@ namespace graphlab { size_t bufsize = 50000; bool usehash = false; bool userecent = false; + // hybrid cut + size_t threshold = 100; + // ginger heuristic + size_t interval = std::numeric_limits::max(); + size_t nedges = 0; + size_t nverts = 0; + std::string ingress_method = ""; std::vector keys = opts.get_graph_args().get_option_keys(); foreach(std::string opt, keys) { @@ -650,30 +668,52 @@ namespace graphlab { if (!parallel_ingress && rpc.procid() == 0) logstream(LOG_EMPH) << "Disable parallel ingress. Graph will be streamed through one node." << std::endl; + } else if (opt == "threshold") { + opts.get_graph_args().get_option("threshold", threshold); + if (rpc.procid() == 0) + logstream(LOG_EMPH) << "Graph Option: threshold = " + << threshold << std::endl; + } else if (opt == "interval") { + opts.get_graph_args().get_option("interval", interval); + if (rpc.procid() == 0) + logstream(LOG_EMPH) << "Graph Option: interval = " + << interval << std::endl; + } else if (opt == "nedges") { + opts.get_graph_args().get_option("nedges", nedges); + if (rpc.procid() == 0) + logstream(LOG_EMPH) << "Graph Option: nedges = " + << nedges << std::endl; + } else if (opt == "nverts") { + opts.get_graph_args().get_option("nverts", nverts); + if (rpc.procid() == 0) + logstream(LOG_EMPH) << "Graph Option: nverts = " + << nverts << std::endl; } + /** * These options below are deprecated. */ else if (opt == "bufsize") { opts.get_graph_args().get_option("bufsize", bufsize); - if (rpc.procid() == 0) + if (rpc.procid() == 0) logstream(LOG_EMPH) << "Graph Option: bufsize = " << bufsize << std::endl; - } else if (opt == "usehash") { + } else if (opt == "usehash") { opts.get_graph_args().get_option("usehash", usehash); if (rpc.procid() == 0) logstream(LOG_EMPH) << "Graph Option: usehash = " << usehash << std::endl; } else if (opt == "userecent") { opts.get_graph_args().get_option("userecent", userecent); - if (rpc.procid() == 0) + if (rpc.procid() == 0) logstream(LOG_EMPH) << "Graph Option: userecent = " << userecent << std::endl; - } else { + } else { logstream(LOG_ERROR) << "Unexpected Graph Option: " << opt << std::endl; } - } - set_ingress_method(ingress_method, bufsize, usehash, userecent); + } + set_ingress_method(ingress_method, bufsize, usehash, userecent, + threshold, nedges, nverts, interval); } public: @@ -892,6 +932,30 @@ namespace graphlab { return true; } + /* used by radj */ + void add_edge(std::vector& sources, vertex_id_type target, + const std::vector& edatas) { +#ifndef USE_DYNAMIC_LOCAL_GRAPH + if(finalized) { + logstream(LOG_FATAL) + << "\n\tAttempting to add an edge to a finalized graph." + << "\n\tEdges cannot be added to a graph after finalization." + << std::endl; + } +#else + finalized = false; +#endif + if(target == vertex_id_type(-1)) { + logstream(LOG_FATAL) + << "\n\tThe target vertex with id vertex_id_type(-1)\n" + << "\tor unsigned value " << vertex_id_type(-1) << " in edge \n" + << "\tThe -1 vertex id is reserved for internal use." + << std::endl; + } + ASSERT_NE(ingress_ptr, NULL); + + ingress_ptr->add_edges(sources, target, edatas); + } /** * \brief Performs a map-reduce operation on each vertex in the @@ -2180,10 +2244,8 @@ namespace graphlab { logstream(LOG_WARNING) << "No files found matching " << original_path << std::endl; } -#ifdef _OPENMP -#pragma omp parallel for -#endif - for(size_t i = 0; i < graph_files.size(); ++i) { + if (get_cuts_type() == HYBRID_GINGER_CUTS) { + for(size_t i = 0; i < graph_files.size(); ++i) { if ((parallel_ingress && (i % rpc.numprocs() == rpc.procid())) || (!parallel_ingress && (rpc.procid() == 0))) { logstream(LOG_EMPH) << "Loading graph from file: " << graph_files[i] << std::endl; @@ -2206,6 +2268,35 @@ namespace graphlab { if (gzip) fin.pop(); } } + } else { +#ifdef _OPENMP +#pragma omp parallel for +#endif + for(size_t i = 0; i < graph_files.size(); ++i) { + if ((parallel_ingress && (i % rpc.numprocs() == rpc.procid())) + || (!parallel_ingress && (rpc.procid() == 0))) { + logstream(LOG_EMPH) << "Loading graph from file: " << graph_files[i] << std::endl; + // is it a gzip file ? + const bool gzip = boost::ends_with(graph_files[i], ".gz"); + // open the stream + std::ifstream in_file(graph_files[i].c_str(), + std::ios_base::in | std::ios_base::binary); + // attach gzip if the file is gzip + boost::iostreams::filtering_stream fin; + // Using gzip filter + if (gzip) fin.push(boost::iostreams::gzip_decompressor()); + fin.push(in_file); + const bool success = load_from_stream(graph_files[i], fin, line_parser); + if(!success) { + logstream(LOG_FATAL) + << "\n\tError parsing file: " << graph_files[i] << std::endl; + } + fin.pop(); + if (gzip) fin.pop(); + } + } + } + rpc.full_barrier(); } // end of load from posixfs @@ -2416,6 +2507,9 @@ namespace graphlab { } else if (format == "adj") { line_parser = builtin_parsers::adj_parser; load(path, line_parser); + } else if (format == "radj") { + line_parser = builtin_parsers::radj_parser; + load(path, line_parser); } else if (format == "tsv") { line_parser = builtin_parsers::tsv_parser; load(path, line_parser); @@ -2596,6 +2690,8 @@ namespace graphlab { struct vertex_record { /// The official owning processor for this vertex procid_t owner; + /// The type of vertex + zone_type type; /// The local vid of this vertex on this proc vertex_id_type gvid; /// The number of in edges @@ -2618,6 +2714,7 @@ namespace graphlab { void load(iarchive& arc) { clear(); arc >> owner + >> type >> gvid >> num_in_edges >> num_out_edges @@ -2626,6 +2723,7 @@ namespace graphlab { void save(oarchive& arc) const { arc << owner + << type << gvid << num_in_edges << num_out_edges @@ -2811,6 +2909,13 @@ namespace graphlab { return lvid2record[lvid].owner; } + /** \internal + * \brief Returns the type of vertex. + */ + zone_type l_type(lvid_type lvid) const { + ASSERT_LT(lvid, lvid2record.size()); + return lvid2record[lvid].type; + } /** \internal * \brief Returns a reference to the internal graph representation @@ -3120,6 +3225,10 @@ namespace graphlab { public: + cuts_type get_cuts_type() const { return how_cuts; } + + void set_cuts_type(cuts_type type) { how_cuts = type; } + // For the warp engine to find the remote instances of this class size_t get_rpc_obj_id() { return rpc.get_obj_id(); @@ -3151,6 +3260,9 @@ namespace graphlab { /** The global number of vertex replica */ size_t nreplicas; + /** The cut type */ + cuts_type how_cuts; + /** pointer to the distributed ingress object*/ distributed_ingress_base* ingress_ptr; @@ -3167,7 +3279,9 @@ namespace graphlab { lock_manager_type lock_manager; void set_ingress_method(const std::string& method, - size_t bufsize = 50000, bool usehash = false, bool userecent = false) { + size_t bufsize = 50000, bool usehash = false, bool userecent = false, + size_t threshold = 100, size_t nedges = 0, size_t nverts = 0, + size_t interval = std::numeric_limits::max()) { if(ingress_ptr != NULL) { delete ingress_ptr; ingress_ptr = NULL; } if (method == "oblivious") { if (rpc.procid() == 0) logstream(LOG_EMPH) << "Use oblivious ingress, usehash: " << usehash @@ -3182,6 +3296,15 @@ namespace graphlab { } else if (method == "pds") { if (rpc.procid() == 0)logstream(LOG_EMPH) << "Use pds ingress" << std::endl; ingress_ptr = new distributed_constrained_random_ingress(rpc.dc(), *this, "pds"); + } else if (method == "hybrid") { + if (rpc.procid() == 0) logstream(LOG_EMPH) << "Use hybrid ingress" << std::endl; + ingress_ptr = new distributed_hybrid_ingress(rpc.dc(), *this, threshold); + set_cuts_type(HYBRID_CUTS); + } else if (method == "hybrid_ginger") { + if (rpc.procid() == 0) logstream(LOG_EMPH) << "Use hybrid ginger ingress" << std::endl; + ASSERT_GT(nedges, 0); ASSERT_GT(nverts, 0); + ingress_ptr = new distributed_hybrid_ginger_ingress(rpc.dc(), *this, threshold, nedges, nverts, interval); + set_cuts_type(HYBRID_GINGER_CUTS); } else { // use default ingress method if none is specified std::string ingress_auto=""; diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp new file mode 100644 index 0000000000..cbd8bd19ee --- /dev/null +++ b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp @@ -0,0 +1,857 @@ +/** + * Copyright (c) 2009 Carnegie Mellon University. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * For more about this software visit: + * + * http://www.graphlab.ml.cmu.edu + * + */ + +#ifndef GRAPHLAB_DISTRIBUTED_HYBRID_GINGER_INGRESS_HPP +#define GRAPHLAB_DISTRIBUTED_HYBRID_GINGER_INGRESS_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace graphlab { + template + class distributed_graph; + + /** + * \brief Ingress object assigning edges using a hybrid method. + * That is, for high degree edges, + * for low degree edges, hashing from its target vertex. + */ + template + class distributed_hybrid_ginger_ingress : + public distributed_ingress_base { + + public: + typedef distributed_graph graph_type; + /// The type of the vertex data stored in the graph + typedef VertexData vertex_data_type; + /// The type of the edge data stored in the graph + typedef EdgeData edge_data_type; + + typedef distributed_ingress_base base_type; + + typedef typename graph_type::vertex_record vertex_record; + typedef typename graph_type::mirror_type mirror_type; + + + typedef typename hopscotch_map::value_type + vid2lvid_pair_type; + + typedef typename buffered_exchange::buffer_type + vertex_id_buffer_type; + + typedef typename graph_type::zone_type zone_type; + + /// one-by-one ingress. e.g., SNAP + typedef typename base_type::edge_buffer_record edge_buffer_record; + typedef typename buffered_exchange::buffer_type + edge_buffer_type; + + + + typedef typename base_type::vertex_buffer_record vertex_buffer_record; + typedef typename buffered_exchange::buffer_type + vertex_buffer_type; + + /// batch ingress. e.g., R-ADJ + struct batch_edge_buffer_record { + std::vector sources; + vertex_id_type target; + std::vector edatas; + + batch_edge_buffer_record( + const std::vector& sources = std::vector() , + const vertex_id_type& target = vertex_id_type(-1), + const std::vector& edatas = std::vector()) : + sources(sources), target(target), edatas(edatas) { } + + void load(iarchive& arc) { arc >> sources >> target >> edatas; } + void save(oarchive& arc) const { arc << sources << target << edatas; } + }; + typedef typename buffered_exchange::buffer_type + batch_edge_buffer_type; + + /// detail vertex record for the second pass coordination. + typedef typename base_type::vertex_negotiator_record + vertex_negotiator_record; + + typedef typename std::pair mht_entry_type; + + /** Type of the master location hash table: + * a map from vertex id to the location of its master. */ + typedef typename boost::unordered_map master_hash_table_type; + typedef typename std::pair master_pair_type; + typedef typename buffered_exchange::buffer_type master_buffer_type; + + typedef typename std::pair proc_edges_pair_type; + typedef typename buffered_exchange::buffer_type + proc_edges_buffer_type; + + /** Number of edges and vertices in graph. */ + buffered_exchange hybrid_edge_exchange; + buffered_exchange hybrid_vertex_exchange; + + /** master hash table: + * mht is the synchronized one across the cluster, + * mht_incremental is the new added mht which stays local since the last sync. + */ + master_hash_table_type mht; + master_hash_table_type mht_incremental; + + buffered_exchange master_exchange; + + /// The rpc interface for this object + dc_dist_object hybrid_rpc; + /// The underlying distributed graph object that is being loaded + graph_type& graph; + + buffered_exchange high_edge_exchange; + buffered_exchange low_edge_exchange; + + //these are used for edges balance + + buffered_exchange< proc_edges_pair_type > proc_edges_exchange; + std::vector proc_edges_incremental; + + std::vector proc_num_vertices; + + /// threshold to divide high-degree and low-degree vertices + size_t threshold; + /// records about the number of edges and vertices in the graph + /// given from the commandline + size_t nedges; + size_t nverts; + /// threshold for incremental mht to be synced across the cluster + /// when the incremental mht size reaches the preset interval, + /// we will perform a synchronization on mht across the cluster + size_t interval; + /// arguments for the ginger algorithm + double alpha; + double gamma; + + + public: + distributed_hybrid_ginger_ingress(distributed_control& dc, graph_type& graph, + size_t threshold = 100, size_t nedges = 0, size_t nverts = 0, + size_t interval = std::numeric_limits::max()) : + base_type(dc, graph), + hybrid_edge_exchange(dc), hybrid_vertex_exchange(dc), + master_exchange(dc), + hybrid_rpc(dc, this),graph(graph),high_edge_exchange(dc),low_edge_exchange(dc),proc_edges_exchange(dc), + proc_edges_incremental(dc.numprocs()),proc_num_vertices(dc.numprocs()), threshold(threshold), + nedges(nedges), nverts(nverts), interval(interval) { + gamma = 1.5; + alpha = sqrt(dc.numprocs()) * nedges / pow(nverts, gamma); + if(nedges<=0) + nedges=1; + } // end of constructor + + ~distributed_hybrid_ginger_ingress() { } + + /** Add an edge to the ingress object using random hashing assignment. + * This function acts as the first phase for SNAP graph to deliver edges + * via the hashing value of its target vertex. + */ + void add_edge(vertex_id_type source, vertex_id_type target, + const EdgeData& edata) { + const procid_t owning_proc = + graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); + const edge_buffer_record record(source, target, edata); + hybrid_edge_exchange.send(owning_proc, record); + } // end of add edge + + /** Add edges to the ingress object using different assignment policies. + * This function handles the RADJ graph in different ways. + * For high degree edges, hashing from its source vertex; + * for low degree edges, using a heuristic way named ginger. + */ + void add_edges(std::vector& in, vertex_id_type vid, + const std::vector& edatas) { + procid_t owning_proc; + if (in.size()>=threshold) { + owning_proc = graph_hash::hash_vertex(vid) % base_type::rpc.numprocs(); + } else { + owning_proc = + base_type::edge_decision.edge_to_proc_ginger(vid, mht, + mht_incremental, proc_num_vertices, alpha, gamma, in); + } + + typedef typename base_type::edge_buffer_record edge_buffer_record; + if(in.size()>=threshold){ + for (size_t i = 0; i < in.size(); ++i) { + edge_buffer_record record(in[i], vid, edatas[i]); + high_edge_exchange.send(owning_proc, record); + } + } + else{ + for (size_t i = 0; i < in.size(); ++i) { + edge_buffer_record record(in[i], vid, edatas[i]); + low_edge_exchange.send(owning_proc, record); + } + + proc_edges_incremental[owning_proc]+=in.size(); //edge balance + } + size_t numprocs = base_type::rpc.numprocs(); + mht_incremental[vid] = owning_proc; + if (mht_incremental.size() > interval) { + //update mht + for (typename master_hash_table_type::iterator it = mht_incremental.begin(); + it != mht_incremental.end(); ++it) { + for (procid_t i = 0; i < numprocs; ++i) { + if (i != hybrid_rpc.procid()) + master_exchange.send(i, master_pair_type(it->first, it->second)); + } + mht[it->first] = it->second; + } + master_exchange.partial_flush(0); + mht_incremental.clear(); + master_buffer_type master_buffer; + procid_t proc; + while(master_exchange.recv(proc, master_buffer)) { + foreach(const master_pair_type& pair, master_buffer) { + mht[pair.first] = pair.second; + //proc_num_vertices[pair.second]++; + } + } + + //update proc_edges for edges balance + for(procid_t p=0;p batch_record_map_type; + batch_record_map_type batch_map; + + hybrid_edge_exchange.flush(); + + edge_buffer_type edge_buffer; + procid_t proc = -1; + while(hybrid_edge_exchange.recv(proc, edge_buffer)) { + foreach(const edge_buffer_record& rec, edge_buffer) { + batch_map[rec.target].sources.push_back(rec.source); + batch_map[rec.target].edatas.push_back(rec.edata); + } + } + hybrid_edge_exchange.clear(); + + hybrid_rpc.full_barrier(); + + for (typename batch_record_map_type::iterator it = batch_map.begin();it != batch_map.end(); ++it) { + add_edges(it->second.sources, it->first,it->second.edatas); + } + } + + void finalize() { + graphlab::timer ti; + + snap_ingress(); + + size_t nprocs = hybrid_rpc.numprocs(); + procid_t l_procid = hybrid_rpc.procid(); + std::vector hybrid_edges; + size_t nedges; + + + { // send mht to all other nodes + for (typename master_hash_table_type::iterator it = mht_incremental.begin(); + it != mht_incremental.end(); ++it) { + for (procid_t i = 0; i < nprocs; ++i) { + if (i != l_procid) + master_exchange.send(i, master_pair_type(it->first, it->second)); + } + mht[it->first] = it->second; + } + + mht_incremental.clear(); + master_exchange.flush(); + + master_buffer_type master_buffer; + procid_t proc; + while(master_exchange.recv(proc, master_buffer)) { + foreach(const master_pair_type& pair, master_buffer) { + mht[pair.first] = pair.second; + } + } + master_exchange.clear(); + + + + std::vector hybrid_edges; + hopscotch_map in_degree_set; + + + if (l_procid == 0) { + memory_info::log_usage("start finalizing"); + logstream(LOG_EMPH) << "hybrid ginger finalizing Graph ..." + << " #vertices=" << graph.local_graph.num_vertices() + << " #edges=" << graph.local_graph.num_edges() + << " threshold=" << threshold + << std::endl; + } + } + + + + + + /**************************************************************************/ + /* */ + /* batch ingress */ + /* */ + /**************************************************************************/ + { + high_edge_exchange.flush(); + low_edge_exchange.flush(); + + nedges=low_edge_exchange.size(); + hybrid_edges.reserve(nedges+high_edge_exchange.size()); + + edge_buffer_type edge_buffer; + procid_t proc = -1; + while(low_edge_exchange.recv(proc, edge_buffer)) { + foreach(const edge_buffer_record& rec, edge_buffer) { + if (mht.find(rec.source) == mht.end()) + mht[rec.source] = graph_hash::hash_vertex(rec.source) % hybrid_rpc.numprocs(); + hybrid_edges.push_back(rec); + } + } + low_edge_exchange.clear(); + + hybrid_rpc.full_barrier(); + + while(high_edge_exchange.recv(proc, edge_buffer)) { + foreach(const edge_buffer_record& rec, edge_buffer) { + if (mht.find(rec.source) == mht.end()) + mht[rec.source] = graph_hash::hash_vertex(rec.source) % hybrid_rpc.numprocs(); + const procid_t source_owner_proc = mht[rec.source]; + + if(source_owner_proc == l_procid){ + hybrid_edges.push_back(rec); + nedges++; + } else { + low_edge_exchange.send(source_owner_proc, rec); + } + } + } + high_edge_exchange.clear(); + + low_edge_exchange.flush(); + + if(l_procid == 0) + logstream(LOG_INFO) << "receive high-degree mirrors: " + << low_edge_exchange.size() << std::endl; + { + edge_buffer_type edge_buffer; + procid_t proc = -1; + while(low_edge_exchange.recv(proc, edge_buffer)) { + foreach(const edge_buffer_record& rec, edge_buffer) { + mht[rec.source] = l_procid; + hybrid_edges.push_back(rec); + nedges++; + } + } + } + low_edge_exchange.clear(); + } + + // connect to base finalize() + // pass nedges and hybrid_edges to the base finalize + modified_base_finalize(nedges, hybrid_edges); + + set_vertex_type(); + + if(l_procid == 0) + logstream(LOG_EMPH) << "orignal hybrid finalizing graph done. (" + << ti.current_time() + << " secs)" + << std::endl; + } // end of finalize + + void set_vertex_type() { + graphlab::timer ti; + procid_t l_procid = hybrid_rpc.procid(); + + for (size_t lvid = 0; lvid < graph.num_local_vertices(); lvid++) { + vertex_record& vrec = graph.lvid2record[lvid]; + if (vrec.owner == l_procid) { + if (vrec.num_in_edges >= threshold) + vrec.type = graph_type::HIGH_MASTER; + else vrec.type = graph_type::LOW_MASTER; + } else { + if (vrec.num_in_edges >= threshold) + vrec.type = graph_type::HIGH_MIRROR; + else vrec.type = graph_type::LOW_MIRROR; + } + } + + if(l_procid == 0) { + memory_info::log_usage("set vertex type done."); + logstream(LOG_EMPH) << "set vertex type: " + << ti.current_time() + << " secs" + << std::endl; + } + } + + /* do the same job as original base finalize except for + * extracting edges from hybrid_edges instead of original edge_buffer; + * and using mht to tracing the master location of each vertex. + */ + void modified_base_finalize(size_t nedges, + std::vector& hybrid_edges) { + + graphlab::timer ti; + procid_t l_procid = hybrid_rpc.procid(); + + hybrid_rpc.full_barrier(); + + bool first_time_finalize = false; + /** + * Fast pass for first time finalization. + */ + if (graph.is_dynamic()) { + size_t nverts = graph.num_local_vertices(); + hybrid_rpc.all_reduce(nverts); + first_time_finalize = (nverts == 0); + } else { + first_time_finalize = false; + } + + /** + * \internal + * Buffer storage for new vertices to the local graph. + */ + typedef typename graph_type::hopscotch_map_type vid2lvid_map_type; + vid2lvid_map_type vid2lvid_buffer; + + /** + * \internal + * The begining id assinged to the first new vertex. + */ + const lvid_type lvid_start = graph.vid2lvid.size(); + + /** + * \internal + * Bit field incidate the vertex that is updated during the ingress. + */ + dense_bitset updated_lvids(graph.vid2lvid.size()); + + + /**************************************************************************/ + /* */ + /* flush any additional data */ + /* */ + /**************************************************************************/ + hybrid_vertex_exchange.flush(); + + + /**************************************************************************/ + /* */ + /* Construct local graph */ + /* */ + /**************************************************************************/ + { + // Add all the edges to the local graph + graph.local_graph.reserve_edge_space(nedges + 1); + + foreach(const edge_buffer_record& rec, hybrid_edges) { + // skip re-sent edges + if (rec.source == vertex_id_type(-1)) continue; + + // Get the source_vlid; + lvid_type source_lvid(-1); + if(graph.vid2lvid.find(rec.source) == graph.vid2lvid.end()) { + if (vid2lvid_buffer.find(rec.source) == vid2lvid_buffer.end()) { + source_lvid = lvid_start + vid2lvid_buffer.size(); + vid2lvid_buffer[rec.source] = source_lvid; + } else { + source_lvid = vid2lvid_buffer[rec.source]; + } + } else { + source_lvid = graph.vid2lvid[rec.source]; + updated_lvids.set_bit(source_lvid); + } + // Get the target_lvid; + lvid_type target_lvid(-1); + if(graph.vid2lvid.find(rec.target) == graph.vid2lvid.end()) { + if (vid2lvid_buffer.find(rec.target) == vid2lvid_buffer.end()) { + target_lvid = lvid_start + vid2lvid_buffer.size(); + vid2lvid_buffer[rec.target] = target_lvid; + } else { + target_lvid = vid2lvid_buffer[rec.target]; + } + } else { + target_lvid = graph.vid2lvid[rec.target]; + updated_lvids.set_bit(target_lvid); + } + graph.local_graph.add_edge(source_lvid, target_lvid, rec.edata); + } // end for loop over buffers + hybrid_edges.clear(); + + ASSERT_EQ(graph.vid2lvid.size() + vid2lvid_buffer.size(), + graph.local_graph.num_vertices()); + if(l_procid == 0) { + memory_info::log_usage("populating local graph done."); + logstream(LOG_EMPH) << "populating local graph: " + << ti.current_time() + << " secs" + << std::endl; + } + + // Finalize local graph + graph.local_graph.finalize(); + logstream(LOG_INFO) << "local graph info: " << std::endl + << "\t nverts: " << graph.local_graph.num_vertices() + << std::endl + << "\t nedges: " << graph.local_graph.num_edges() + << std::endl; + + if(l_procid == 0) { + memory_info::log_usage("finalizing local graph done."); + logstream(LOG_EMPH) << "finalizing local graph: " + << ti.current_time() + << " secs" + << std::endl; + } + } + + + /**************************************************************************/ + /* */ + /* receive and add vertex data to masters */ + /* */ + /**************************************************************************/ + // Setup the map containing all the vertices being negotiated by this machine + { + // receive any vertex data sent by other machines + if (hybrid_vertex_exchange.size() > 0) { + vertex_buffer_type vertex_buffer; procid_t sending_proc(-1); + while(hybrid_vertex_exchange.recv(sending_proc, vertex_buffer)) { + foreach(const vertex_buffer_record& rec, vertex_buffer) { + lvid_type lvid(-1); + if (graph.vid2lvid.find(rec.vid) == graph.vid2lvid.end()) { + if (vid2lvid_buffer.find(rec.vid) == vid2lvid_buffer.end()) { + lvid = lvid_start + vid2lvid_buffer.size(); + vid2lvid_buffer[rec.vid] = lvid; + } else { + lvid = vid2lvid_buffer[rec.vid]; + } + } else { + lvid = graph.vid2lvid[rec.vid]; + updated_lvids.set_bit(lvid); + } + if (distributed_hybrid_ginger_ingress::vertex_combine_strategy + && lvid < graph.num_local_vertices()) { + distributed_hybrid_ginger_ingress::vertex_combine_strategy( + graph.l_vertex(lvid).data(), rec.vdata); + } else { + graph.local_graph.add_vertex(lvid, rec.vdata); + } + } + } + hybrid_vertex_exchange.clear(); + + logstream(LOG_INFO) << " #vert-msgs=" << hybrid_vertex_exchange.size() + << std::endl; + if(l_procid == 0) { + memory_info::log_usage("adding vertex data done."); + logstream(LOG_EMPH) << "adding vertex data: " + << ti.current_time() + << " secs" + << std::endl; + } + } + } // end of loop to populate vrecmap + + + /**************************************************************************/ + /* */ + /* assign vertex data and allocate vertex (meta)data space */ + /* */ + /**************************************************************************/ + +#ifdef INGRESS_DEBUG + std::vector degree; + degree.resize(graph_type::NUM_ZONE_TYPES, 0); +#endif + { + // determine masters for all negotiated vertices + const size_t local_nverts = graph.vid2lvid.size() + vid2lvid_buffer.size(); + graph.lvid2record.reserve(local_nverts); + graph.lvid2record.resize(local_nverts); + graph.local_graph.resize(local_nverts); + foreach(const vid2lvid_pair_type& pair, vid2lvid_buffer) { + vertex_record& vrec = graph.lvid2record[pair.second]; + vrec.gvid = pair.first; + if (mht.find(pair.first) == mht.end()) + mht[pair.first] = graph_hash::hash_vertex(pair.first) % hybrid_rpc.numprocs(); + const procid_t source_owner_proc = mht[pair.first]; + vrec.owner = source_owner_proc; + } + ASSERT_EQ(local_nverts, graph.local_graph.num_vertices()); + ASSERT_EQ(graph.lvid2record.size(), graph.local_graph.num_vertices()); + if(l_procid == 0) { + memory_info::log_usage("allocating lvid2record done."); + logstream(LOG_EMPH) << "allocating lvid2record: " + << ti.current_time() + << " secs" + << std::endl; + } + + mht.clear(); + } + + /**************************************************************************/ + /* */ + /* master handshake */ + /* */ + /**************************************************************************/ + { +#ifdef _OPENMP + buffered_exchange vid_buffer(hybrid_rpc.dc(), omp_get_max_threads()); +#else + buffered_exchange vid_buffer(hybrid_rpc.dc()); +#endif + +#ifdef _OPENMP +#pragma omp parallel for +#endif + // send not owned vids to their master + for (lvid_type i = lvid_start; i < graph.lvid2record.size(); ++i) { + procid_t master = graph.lvid2record[i].owner; + if (master != l_procid) +#ifdef _OPENMP + vid_buffer.send(master, graph.lvid2record[i].gvid, omp_get_thread_num()); +#else + vid_buffer.send(master, graph.lvid2record[i].gvid); +#endif + } + vid_buffer.flush(); + hybrid_rpc.barrier(); + + // receive all vids owned by me + mutex flying_vids_lock; + boost::unordered_map flying_vids; +#ifdef _OPENMP +#pragma omp parallel +#endif + { + typename buffered_exchange::buffer_type buffer; + procid_t recvid = -1; + while(vid_buffer.recv(recvid, buffer)) { + foreach(const vertex_id_type vid, buffer) { + if (graph.vid2lvid.find(vid) == graph.vid2lvid.end()) { + if (vid2lvid_buffer.find(vid) == vid2lvid_buffer.end()) { + flying_vids_lock.lock(); + mirror_type& mirrors = flying_vids[vid]; + mirrors.set_bit(recvid); + flying_vids_lock.unlock(); + } else { + lvid_type lvid = vid2lvid_buffer[vid]; + graph.lvid2record[lvid]._mirrors.set_bit(recvid); + } + } else { + lvid_type lvid = graph.vid2lvid[vid]; + graph.lvid2record[lvid]._mirrors.set_bit(recvid); + updated_lvids.set_bit(lvid); + } + } + } + } + vid_buffer.clear(); + + if (!flying_vids.empty()) { + logstream(LOG_INFO) << "#flying-own-nverts=" + << flying_vids.size() + << std::endl; + + // reallocate spaces for the flying vertices. + size_t vsize_old = graph.lvid2record.size(); + size_t vsize_new = vsize_old + flying_vids.size(); + graph.lvid2record.resize(vsize_new); + graph.local_graph.resize(vsize_new); + for (typename boost::unordered_map::iterator it = flying_vids.begin(); + it != flying_vids.end(); ++it) { + lvid_type lvid = lvid_start + vid2lvid_buffer.size(); + vertex_record& vrec = graph.lvid2record[lvid]; + vertex_id_type gvid = it->first; + vrec.owner = l_procid; + vrec.gvid = gvid; + vrec._mirrors = it->second; + vid2lvid_buffer[gvid] = lvid; + } + } + } // end of master handshake + + if(l_procid == 0) { + memory_info::log_usage("master handshake done."); + logstream(LOG_EMPH) << "master handshake: " + << ti.current_time() + << " secs" + << std::endl; + } + + + /**************************************************************************/ + /* */ + /* merge in vid2lvid_buffer */ + /* */ + /**************************************************************************/ + { + if (graph.vid2lvid.size() == 0) { + graph.vid2lvid.swap(vid2lvid_buffer); + } else { + graph.vid2lvid.rehash(graph.vid2lvid.size() + vid2lvid_buffer.size()); + foreach (const typename vid2lvid_map_type::value_type& pair, vid2lvid_buffer) { + graph.vid2lvid.insert(pair); + } + vid2lvid_buffer.clear(); + } + } + + + /**************************************************************************/ + /* */ + /* synchronize vertex data and meta information */ + /* */ + /**************************************************************************/ + { + // construct the vertex set of changed vertices + + // Fast pass for first time finalize; + vertex_set changed_vset(true); + + // Compute the vertices that needs synchronization + if (!first_time_finalize) { + vertex_set changed_vset = vertex_set(false); + changed_vset.make_explicit(graph); + updated_lvids.resize(graph.num_local_vertices()); + for (lvid_type i = lvid_start; i < graph.num_local_vertices(); ++i) { + updated_lvids.set_bit(i); + } + changed_vset.localvset = updated_lvids; + buffered_exchange vset_exchange(hybrid_rpc.dc()); + // sync vset with all mirrors + changed_vset.synchronize_mirrors_to_master_or(graph, vset_exchange); + changed_vset.synchronize_master_to_mirrors(graph, vset_exchange); + } + + graphlab::graph_gather_apply + vrecord_sync_gas(graph, + boost::bind(&distributed_hybrid_ginger_ingress::finalize_gather, this, _1, _2), + boost::bind(&distributed_hybrid_ginger_ingress::finalize_apply, this, _1, _2, _3)); + vrecord_sync_gas.exec(changed_vset); + + if(l_procid == 0) { + memory_info::log_usage("synchronizing vertex (meta)data done."); + logstream(LOG_EMPH) << "synchrionizing vertex (meta)data: " + << ti.current_time() + << " secs" + << std::endl; + } + } + + base_type::exchange_global_info(); + if(l_procid == 0) { + memory_info::log_usage("exchange global info done."); + logstream(LOG_EMPH) << "exchange global info: " + << ti.current_time() + << " secs" + << std::endl; + } + } // end of modified base finalize + + private: + boost::function vertex_combine_strategy; + + /** + * \brief Gather the vertex distributed meta data. + */ + vertex_negotiator_record finalize_gather(lvid_type& lvid, graph_type& graph) { + vertex_negotiator_record accum; + accum.num_in_edges = graph.local_graph.num_in_edges(lvid); + accum.num_out_edges = graph.local_graph.num_out_edges(lvid); + if (graph.l_is_master(lvid)) { + accum.has_data = true; + accum.vdata = graph.l_vertex(lvid).data(); + accum.mirrors = graph.lvid2record[lvid]._mirrors; + } + return accum; + } + + /** + * \brief Update the vertex data structures with the gathered vertex metadata. + */ + void finalize_apply(lvid_type lvid, const vertex_negotiator_record& accum, graph_type& graph) { + typename graph_type::vertex_record& vrec = graph.lvid2record[lvid]; + vrec.num_in_edges = accum.num_in_edges; + vrec.num_out_edges = accum.num_out_edges; + graph.l_vertex(lvid).data() = accum.vdata; + vrec._mirrors = accum.mirrors; + } + }; // end of distributed_hybrid_ginger_ingress +}; // end of namespace graphlab +#include + + +#endif diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp new file mode 100644 index 0000000000..82946f7d6f --- /dev/null +++ b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp @@ -0,0 +1,817 @@ +/** + * Copyright (c) 2009 Carnegie Mellon University. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * For more about this software visit: + * + * http://www.graphlab.ml.cmu.edu + * + */ + +#ifndef GRAPHLAB_DISTRIBUTED_HYBRID_INGRESS_HPP +#define GRAPHLAB_DISTRIBUTED_HYBRID_INGRESS_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#undef TUNING +namespace graphlab { + template + class distributed_graph; + + /** + * \brief Ingress object assigning edges using a hybrid method. + * That is, for high degree edge, hashing from its source vertex; + * for low degree edge, hashing from its target vertex. + */ + template + class distributed_hybrid_ingress : + public distributed_ingress_base { + public: + typedef distributed_graph graph_type; + /// The type of the vertex data stored in the graph + typedef VertexData vertex_data_type; + /// The type of the edge data stored in the graph + typedef EdgeData edge_data_type; + + typedef distributed_ingress_base base_type; + + typedef typename graph_type::vertex_record vertex_record; + typedef typename graph_type::mirror_type mirror_type; + + + typedef typename hopscotch_map::value_type + vid2lvid_pair_type; + + typedef typename buffered_exchange::buffer_type + vertex_id_buffer_type; + + typedef typename graph_type::zone_type zone_type; + + /// The rpc interface for this object + dc_dist_object hybrid_rpc; + /// The underlying distributed graph object that is being loaded + graph_type& graph; + + + /// one-by-one ingress. e.g., SNAP + typedef typename base_type::edge_buffer_record edge_buffer_record; + typedef typename buffered_exchange::buffer_type + edge_buffer_type; + + buffered_exchange hybrid_edge_exchange; + + typedef typename base_type::vertex_buffer_record vertex_buffer_record; + typedef typename buffered_exchange::buffer_type + vertex_buffer_type; + + buffered_exchange hybrid_vertex_exchange; + + /// batch ingress. e.g., R-ADJ + struct batch_edge_buffer_record { + std::vector sources; + vertex_id_type target; + std::vector edatas; + + batch_edge_buffer_record( + const std::vector& sources = std::vector() , + const vertex_id_type& target = vertex_id_type(-1), + const std::vector& edatas = std::vector()) : + sources(sources), target(target), edatas(edatas) { } + + void load(iarchive& arc) { arc >> sources >> target >> edatas; } + void save(oarchive& arc) const { arc << sources << target << edatas; } + }; + typedef typename buffered_exchange::buffer_type + batch_edge_buffer_type; + + buffered_exchange high_batch_edge_exchange; + buffered_exchange low_batch_edge_exchange; + + + /// detail vertex record for the second pass coordination. + typedef typename base_type::vertex_negotiator_record + vertex_negotiator_record; + + + /// threshold to divide high-degree and low-degree vertices + size_t threshold; + + public: + distributed_hybrid_ingress(distributed_control& dc, + graph_type& graph, size_t threshold = 100) : + base_type(dc, graph), hybrid_rpc(dc, this), graph(graph), + hybrid_edge_exchange(dc), hybrid_vertex_exchange(dc), + high_batch_edge_exchange(dc), low_batch_edge_exchange(dc), + threshold(threshold) { + hybrid_rpc.barrier(); + } // end of constructor + + ~distributed_hybrid_ingress() { } + + /** Add an edge to the ingress object using random hashing assignment. + * This function acts as the first phase for SNAP graph to deliver edges + * via the hashing value of its target vertex. + */ + void add_edge(vertex_id_type source, vertex_id_type target, + const EdgeData& edata) { + const procid_t owning_proc = + graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); + const edge_buffer_record record(source, target, edata); + hybrid_edge_exchange.send(owning_proc, record); + } // end of add edge + + + /** Add edges to the ingress object using different assignment policies. + * This function handles the RADJ graph in different ways. + * For high degree edges, hashing from its source vertex; + * for low degree edges, hasing from its target vertex. + */ + void add_edges(std::vector& sources, vertex_id_type target, + const std::vector& edatas) { + const procid_t target_owner_proc = + graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); + + if(sources.size()>=threshold){ + std::vector batch_rec_vector(hybrid_rpc.numprocs()); + + for(size_t i = 0; i < sources.size(); i++){ + const procid_t source_owner_proc = + graph_hash::hash_vertex(sources[i]) % hybrid_rpc.numprocs(); + batch_rec_vector[source_owner_proc].sources.push_back(sources[i]); + batch_rec_vector[source_owner_proc].edatas.push_back(edatas[i]); + } + + for(size_t i = 0; i < batch_rec_vector.size(); i++){ + if(batch_rec_vector[i].sources.size() > 0){ + batch_rec_vector[i].target=target; + high_batch_edge_exchange.send((procid_t) i, batch_rec_vector[i]); + } + } + } + else{ + const batch_edge_buffer_record record(sources, target, edatas); + low_batch_edge_exchange.send(target_owner_proc, record); + } + } // end of add edges + + /* add vdata */ + void add_vertex(vertex_id_type vid, const VertexData& vdata) { + const procid_t owning_proc = + graph_hash::hash_vertex(vid) % hybrid_rpc.numprocs(); + const vertex_buffer_record record(vid, vdata); + hybrid_vertex_exchange.send(owning_proc, record); + } // end of add vertex + + + void finalize() { + + graphlab::timer ti; + + size_t nprocs = hybrid_rpc.numprocs(); + procid_t l_procid = hybrid_rpc.procid(); + + std::vector hybrid_edges; + hopscotch_map in_degree_set; + size_t nedges; + + if (l_procid == 0) { + memory_info::log_usage("start finalizing"); + logstream(LOG_EMPH) << "hybrid orignal finalizing Graph ..." + << " #vertices=" << graph.local_graph.num_vertices() + << " #edges=" << graph.local_graph.num_edges() + << " threshold=" << threshold + << std::endl; + } + + /**************************************************************************/ + /* */ + /* one-by-one ingress */ + /* */ + /**************************************************************************/ + /* This is an extra phase for SNAP graph. + * In this phase, we edges in hybrid_edge_exchange buffer to hybrid_edges + * and construct the in_degree_set at the same time. + */ + { + hybrid_edge_exchange.flush(); + nedges = hybrid_edge_exchange.size(); + hybrid_edges.reserve(nedges); +#ifdef TUNING + logstream(LOG_INFO) << "#" << l_procid << " receive " + << nedges + << " edge-msgs" + << std::endl; + + if (l_procid == 0) { + memory_info::log_usage("edge_exchange flush done."); + logstream(LOG_EMPH) << "edge_exchange flush: " + << ti.current_time() + << " secs" + << std::endl; + } +#endif + edge_buffer_type edge_buffer; + procid_t proc = -1; + while(hybrid_edge_exchange.recv(proc, edge_buffer)) { + foreach(const edge_buffer_record& rec, edge_buffer) { + hybrid_edges.push_back(rec); + in_degree_set[rec.target]++; + } + } + hybrid_edge_exchange.clear(); + // sync before reusing + hybrid_edge_exchange.barrier(); +#ifdef TUNING + if(l_procid == 0) { + memory_info::log_usage("save local edges and count in-degree."); + logstream(LOG_EMPH) << "save local edges and count in-degree: " + << ti.current_time() + << " secs" + << std::endl; + } +#endif + + // classify and record vertices + for (size_t i = 0; i < hybrid_edges.size(); i++) { + edge_buffer_record& rec = hybrid_edges[i]; + const procid_t source_owner_proc = + graph_hash::hash_vertex(rec.source) % nprocs; + + // high-degree target vertex + if (in_degree_set[rec.target] >= threshold) { + if(source_owner_proc != l_procid){ + // re-send the edge of high-degree vertices according to source + hybrid_edge_exchange.send(source_owner_proc, rec); + // set re-sent edges as empty for skipping + hybrid_edges[i] = edge_buffer_record(); + --nedges; + } + } + } +#ifdef TUNING + if(l_procid == 0) { + memory_info::log_usage("store vertex and resend done."); + logstream(LOG_EMPH) << "store vertex and resend: " + << ti.current_time() + << " secs" + << std::endl; + } +#endif + // receive re-sent edges and high-degree mirrors + hybrid_edge_exchange.flush(); +#ifdef TUNING + if(l_procid == 0) + logstream(LOG_INFO) << "receive high-degree edges: " + << hybrid_edge_exchange.size() << std::endl; +#endif + proc = -1; + while(hybrid_edge_exchange.recv(proc, edge_buffer)) { + foreach(const edge_buffer_record& rec, edge_buffer) { + hybrid_edges.push_back(rec); + ++nedges; + } + } + hybrid_edge_exchange.clear(); + in_degree_set.clear(); +#ifdef TUNING + if(l_procid == 0) { + memory_info::log_usage("receive high-degree mirrors done."); + logstream(LOG_EMPH) << "receive high-degree mirrors: " + << ti.current_time() + << " secs" + << std::endl; + } +#endif + } + + + /**************************************************************************/ + /* */ + /* batch ingress */ + /* */ + /**************************************************************************/ + /* This is an extra phase for RADJ graph. + * In this phase, we move edges from low_batch and high_batch buffers into + * hybrid_edges + */ + { + low_batch_edge_exchange.flush(); + if (low_batch_edge_exchange.size() > 0) { +#ifdef TUNING + logstream(LOG_INFO) << "#" << l_procid << " receive " + << low_batch_edge_exchange.size() + << " low-batch-edge-msgs" + << std::endl; +#endif + batch_edge_buffer_type batch_edge_buffer; + procid_t proc = -1; + while(low_batch_edge_exchange.recv(proc, batch_edge_buffer)) { + foreach(const batch_edge_buffer_record& batch_rec, batch_edge_buffer) { + nedges += batch_rec.sources.size(); + for(size_t i = 0; i < batch_rec.sources.size();i++){ + edge_buffer_record rec(batch_rec.sources[i], + batch_rec.target, + batch_rec.edatas[i]); + hybrid_edges.push_back(rec); + } + } + } + low_batch_edge_exchange.clear(); + } + + + high_batch_edge_exchange.flush(); + if (high_batch_edge_exchange.size() > 0) { +#ifdef TUNING + logstream(LOG_INFO) << "#" << l_procid << " receive " + << high_batch_edge_exchange.size() + << " high-batch-edge-msgs " + << std::endl; +#endif + batch_edge_buffer_type batch_edge_buffer; + procid_t proc = -1; + while(high_batch_edge_exchange.recv(proc, batch_edge_buffer)) { + foreach(const batch_edge_buffer_record& batch_rec, batch_edge_buffer) { + nedges += batch_rec.sources.size(); + for(size_t i = 0; i < batch_rec.sources.size(); i++){ + edge_buffer_record rec(batch_rec.sources[i], + batch_rec.target, + batch_rec.edatas[i]); + hybrid_edges.push_back(rec); + } + } + } + high_batch_edge_exchange.clear(); + } + } + + // connect to base finalize() + // pass nedges and hybrid_edges to the base finalize + modified_base_finalize(nedges, hybrid_edges); + + set_vertex_type(); +#ifdef TUNING + if(l_procid == 0) + logstream(LOG_EMPH) << "orignal hybrid finalizing graph done. (" + << ti.current_time() + << " secs)" + << std::endl; +#endif + } // end of finalize + + void set_vertex_type() { + graphlab::timer ti; + procid_t l_procid = hybrid_rpc.procid(); + + for (size_t lvid = 0; lvid < graph.num_local_vertices(); lvid++) { + vertex_record& vrec = graph.lvid2record[lvid]; + if (vrec.owner == l_procid) { + if (vrec.num_in_edges >= threshold) + vrec.type = graph_type::HIGH_MASTER; + else vrec.type = graph_type::LOW_MASTER; + } else { + if (vrec.num_in_edges >= threshold) + vrec.type = graph_type::HIGH_MIRROR; + else vrec.type = graph_type::LOW_MIRROR; + } + } +#ifdef TUNING + if(l_procid == 0) { + memory_info::log_usage("set vertex type done."); + logstream(LOG_EMPH) << "set vertex type: " + << ti.current_time() + << " secs" + << std::endl; + } +#endif + } + + /* do the same job as original base finalize except for + * extracting edges from hybrid_edges instead of original edge_buffer + */ + void modified_base_finalize(size_t nedges, + std::vector& hybrid_edges) { + + graphlab::timer ti; + procid_t l_procid = hybrid_rpc.procid(); + + hybrid_rpc.full_barrier(); + + bool first_time_finalize = false; + /** + * Fast pass for first time finalization. + */ + if (graph.is_dynamic()) { + size_t nverts = graph.num_local_vertices(); + hybrid_rpc.all_reduce(nverts); + first_time_finalize = (nverts == 0); + } else { + first_time_finalize = false; + } + + /** + * \internal + * Buffer storage for new vertices to the local graph. + */ + typedef typename graph_type::hopscotch_map_type vid2lvid_map_type; + vid2lvid_map_type vid2lvid_buffer; + + /** + * \internal + * The begining id assinged to the first new vertex. + */ + const lvid_type lvid_start = graph.vid2lvid.size(); + + /** + * \internal + * Bit field incidate the vertex that is updated during the ingress. + */ + dense_bitset updated_lvids(graph.vid2lvid.size()); + + + /**************************************************************************/ + /* */ + /* flush any additional data */ + /* */ + /**************************************************************************/ + hybrid_vertex_exchange.flush(); + + + /**************************************************************************/ + /* */ + /* Construct local graph */ + /* */ + /**************************************************************************/ + { + // Add all the edges to the local graph + graph.local_graph.reserve_edge_space(nedges + 1); + + foreach(const edge_buffer_record& rec, hybrid_edges) { + // skip re-sent edges + if (rec.source == vertex_id_type(-1)) continue; + + // Get the source_vlid; + lvid_type source_lvid(-1); + if(graph.vid2lvid.find(rec.source) == graph.vid2lvid.end()) { + if (vid2lvid_buffer.find(rec.source) == vid2lvid_buffer.end()) { + source_lvid = lvid_start + vid2lvid_buffer.size(); + vid2lvid_buffer[rec.source] = source_lvid; + } else { + source_lvid = vid2lvid_buffer[rec.source]; + } + } else { + source_lvid = graph.vid2lvid[rec.source]; + updated_lvids.set_bit(source_lvid); + } + // Get the target_lvid; + lvid_type target_lvid(-1); + if(graph.vid2lvid.find(rec.target) == graph.vid2lvid.end()) { + if (vid2lvid_buffer.find(rec.target) == vid2lvid_buffer.end()) { + target_lvid = lvid_start + vid2lvid_buffer.size(); + vid2lvid_buffer[rec.target] = target_lvid; + } else { + target_lvid = vid2lvid_buffer[rec.target]; + } + } else { + target_lvid = graph.vid2lvid[rec.target]; + updated_lvids.set_bit(target_lvid); + } + graph.local_graph.add_edge(source_lvid, target_lvid, rec.edata); + } // end for loop over buffers + hybrid_edges.clear(); + + ASSERT_EQ(graph.vid2lvid.size() + vid2lvid_buffer.size(), + graph.local_graph.num_vertices()); +#ifdef TUNING + if(l_procid == 0) { + memory_info::log_usage("populating local graph done."); + logstream(LOG_EMPH) << "populating local graph: " + << ti.current_time() + << " secs" + << std::endl; + } +#endif + // Finalize local graph + graph.local_graph.finalize(); +#ifdef TUNING + logstream(LOG_INFO) << "local graph info: " << std::endl + << "\t nverts: " << graph.local_graph.num_vertices() + << std::endl + << "\t nedges: " << graph.local_graph.num_edges() + << std::endl; + + if(l_procid == 0) { + memory_info::log_usage("finalizing local graph done."); + logstream(LOG_EMPH) << "finalizing local graph: " + << ti.current_time() + << " secs" + << std::endl; + } +#endif + } + + + /**************************************************************************/ + /* */ + /* receive and add vertex data to masters */ + /* */ + /**************************************************************************/ + // Setup the map containing all the vertices being negotiated by this machine + { + // receive any vertex data sent by other machines + if (hybrid_vertex_exchange.size() > 0) { + vertex_buffer_type vertex_buffer; procid_t sending_proc(-1); + while(hybrid_vertex_exchange.recv(sending_proc, vertex_buffer)) { + foreach(const vertex_buffer_record& rec, vertex_buffer) { + lvid_type lvid(-1); + if (graph.vid2lvid.find(rec.vid) == graph.vid2lvid.end()) { + if (vid2lvid_buffer.find(rec.vid) == vid2lvid_buffer.end()) { + lvid = lvid_start + vid2lvid_buffer.size(); + vid2lvid_buffer[rec.vid] = lvid; + } else { + lvid = vid2lvid_buffer[rec.vid]; + } + } else { + lvid = graph.vid2lvid[rec.vid]; + updated_lvids.set_bit(lvid); + } + if (distributed_hybrid_ingress::vertex_combine_strategy + && lvid < graph.num_local_vertices()) { + distributed_hybrid_ingress::vertex_combine_strategy( + graph.l_vertex(lvid).data(), rec.vdata); + } else { + graph.local_graph.add_vertex(lvid, rec.vdata); + } + } + } + hybrid_vertex_exchange.clear(); +#ifdef TUNING + logstream(LOG_INFO) << " #vert-msgs=" << hybrid_vertex_exchange.size() + << std::endl; + if(l_procid == 0) { + memory_info::log_usage("adding vertex data done."); + logstream(LOG_EMPH) << "adding vertex data: " + << ti.current_time() + << " secs" + << std::endl; + } +#endif + } + } // end of loop to populate vrecmap + + + /**************************************************************************/ + /* */ + /* assign vertex data and allocate vertex (meta)data space */ + /* */ + /**************************************************************************/ + +#ifdef INGRESS_DEBUG + std::vector degree; + degree.resize(graph_type::NUM_ZONE_TYPES, 0); +#endif + { + // determine masters for all negotiated vertices + const size_t local_nverts = graph.vid2lvid.size() + vid2lvid_buffer.size(); + graph.lvid2record.reserve(local_nverts); + graph.lvid2record.resize(local_nverts); + graph.local_graph.resize(local_nverts); + foreach(const vid2lvid_pair_type& pair, vid2lvid_buffer) { + vertex_record& vrec = graph.lvid2record[pair.second]; + vrec.gvid = pair.first; + vrec.owner = graph_hash::hash_vertex(pair.first) % hybrid_rpc.numprocs(); + } + ASSERT_EQ(local_nverts, graph.local_graph.num_vertices()); + ASSERT_EQ(graph.lvid2record.size(), graph.local_graph.num_vertices()); +#ifdef TUNING + if(l_procid == 0) { + memory_info::log_usage("allocating lvid2record done."); + logstream(LOG_EMPH) << "allocating lvid2record: " + << ti.current_time() + << " secs" + << std::endl; + } +#endif + } + + /**************************************************************************/ + /* */ + /* master handshake */ + /* */ + /**************************************************************************/ + { +#ifdef _OPENMP + buffered_exchange vid_buffer(hybrid_rpc.dc(), omp_get_max_threads()); +#else + buffered_exchange vid_buffer(hybrid_rpc.dc()); +#endif + +#ifdef _OPENMP +#pragma omp parallel for +#endif + // send not owned vids to their master + for (lvid_type i = lvid_start; i < graph.lvid2record.size(); ++i) { + procid_t master = graph.lvid2record[i].owner; + if (master != l_procid) +#ifdef _OPENMP + vid_buffer.send(master, graph.lvid2record[i].gvid, omp_get_thread_num()); +#else + vid_buffer.send(master, graph.lvid2record[i].gvid); +#endif + } + vid_buffer.flush(); + hybrid_rpc.barrier(); + + // receive all vids owned by me + mutex flying_vids_lock; + boost::unordered_map flying_vids; +#ifdef _OPENMP +#pragma omp parallel +#endif + { + typename buffered_exchange::buffer_type buffer; + procid_t recvid = -1; + while(vid_buffer.recv(recvid, buffer)) { + foreach(const vertex_id_type vid, buffer) { + if (graph.vid2lvid.find(vid) == graph.vid2lvid.end()) { + if (vid2lvid_buffer.find(vid) == vid2lvid_buffer.end()) { + flying_vids_lock.lock(); + mirror_type& mirrors = flying_vids[vid]; + mirrors.set_bit(recvid); + flying_vids_lock.unlock(); + } else { + lvid_type lvid = vid2lvid_buffer[vid]; + graph.lvid2record[lvid]._mirrors.set_bit(recvid); + } + } else { + lvid_type lvid = graph.vid2lvid[vid]; + graph.lvid2record[lvid]._mirrors.set_bit(recvid); + updated_lvids.set_bit(lvid); + } + } + } + } + vid_buffer.clear(); + + if (!flying_vids.empty()) { + logstream(LOG_INFO) << "#flying-own-nverts=" + << flying_vids.size() + << std::endl; + + // reallocate spaces for the flying vertices. + size_t vsize_old = graph.lvid2record.size(); + size_t vsize_new = vsize_old + flying_vids.size(); + graph.lvid2record.resize(vsize_new); + graph.local_graph.resize(vsize_new); + for (typename boost::unordered_map::iterator it = flying_vids.begin(); + it != flying_vids.end(); ++it) { + lvid_type lvid = lvid_start + vid2lvid_buffer.size(); + vertex_record& vrec = graph.lvid2record[lvid]; + vertex_id_type gvid = it->first; + vrec.owner = l_procid; + vrec.gvid = gvid; + vrec._mirrors = it->second; + vid2lvid_buffer[gvid] = lvid; + } + } + } // end of master handshake + +#ifdef TUNING + if(l_procid == 0) { + memory_info::log_usage("master handshake done."); + logstream(LOG_EMPH) << "master handshake: " + << ti.current_time() + << " secs" + << std::endl; + } +#endif + + /**************************************************************************/ + /* */ + /* merge in vid2lvid_buffer */ + /* */ + /**************************************************************************/ + { + if (graph.vid2lvid.size() == 0) { + graph.vid2lvid.swap(vid2lvid_buffer); + } else { + graph.vid2lvid.rehash(graph.vid2lvid.size() + vid2lvid_buffer.size()); + foreach (const typename vid2lvid_map_type::value_type& pair, vid2lvid_buffer) { + graph.vid2lvid.insert(pair); + } + vid2lvid_buffer.clear(); + } + } + + + /**************************************************************************/ + /* */ + /* synchronize vertex data and meta information */ + /* */ + /**************************************************************************/ + { + // construct the vertex set of changed vertices + + // Fast pass for first time finalize; + vertex_set changed_vset(true); + + // Compute the vertices that needs synchronization + if (!first_time_finalize) { + vertex_set changed_vset = vertex_set(false); + changed_vset.make_explicit(graph); + updated_lvids.resize(graph.num_local_vertices()); + for (lvid_type i = lvid_start; i < graph.num_local_vertices(); ++i) { + updated_lvids.set_bit(i); + } + changed_vset.localvset = updated_lvids; + buffered_exchange vset_exchange(hybrid_rpc.dc()); + // sync vset with all mirrors + changed_vset.synchronize_mirrors_to_master_or(graph, vset_exchange); + changed_vset.synchronize_master_to_mirrors(graph, vset_exchange); + } + + graphlab::graph_gather_apply + vrecord_sync_gas(graph, + boost::bind(&distributed_hybrid_ingress::finalize_gather, this, _1, _2), + boost::bind(&distributed_hybrid_ingress::finalize_apply, this, _1, _2, _3)); + vrecord_sync_gas.exec(changed_vset); +#ifdef TUNING + if(l_procid == 0) { + memory_info::log_usage("synchronizing vertex (meta)data done."); + logstream(LOG_EMPH) << "synchrionizing vertex (meta)data: " + << ti.current_time() + << " secs" + << std::endl; + } +#endif + } + + base_type::exchange_global_info(); +#ifdef TUNING + if(l_procid == 0) { + memory_info::log_usage("exchange global info done."); + logstream(LOG_EMPH) << "exchange global info: " + << ti.current_time() + << " secs" + << std::endl; + } +#endif + } // end of modified base finalize + + private: + boost::function vertex_combine_strategy; + + /** + * \brief Gather the vertex distributed meta data. + */ + vertex_negotiator_record finalize_gather(lvid_type& lvid, graph_type& graph) { + vertex_negotiator_record accum; + accum.num_in_edges = graph.local_graph.num_in_edges(lvid); + accum.num_out_edges = graph.local_graph.num_out_edges(lvid); + if (graph.l_is_master(lvid)) { + accum.has_data = true; + accum.vdata = graph.l_vertex(lvid).data(); + accum.mirrors = graph.lvid2record[lvid]._mirrors; + } + return accum; + } + + /** + * \brief Update the vertex data structures with the gathered vertex metadata. + */ + void finalize_apply(lvid_type lvid, const vertex_negotiator_record& accum, graph_type& graph) { + typename graph_type::vertex_record& vrec = graph.lvid2record[lvid]; + vrec.num_in_edges = accum.num_in_edges; + vrec.num_out_edges = accum.num_out_edges; + graph.l_vertex(lvid).data() = accum.vdata; + vrec._mirrors = accum.mirrors; + } + }; // end of distributed_hybrid_ingress +}; // end of namespace graphlab +#include + + +#endif diff --git a/src/graphlab/graph/ingress/distributed_ingress_base.hpp b/src/graphlab/graph/ingress/distributed_ingress_base.hpp index ce4fefaff1..f2cdc574d2 100644 --- a/src/graphlab/graph/ingress/distributed_ingress_base.hpp +++ b/src/graphlab/graph/ingress/distributed_ingress_base.hpp @@ -129,6 +129,11 @@ namespace graphlab { edge_exchange.send(owning_proc, record); } // end of add edge + /** Stub for ginger ingress. */ + virtual void add_edges( + std::vector& in, + vertex_id_type target, + const std::vector& edatas) { } /** \brief Add an vertex to the ingress object. */ virtual void add_vertex(vertex_id_type vid, const VertexData& vdata) { diff --git a/src/graphlab/graph/ingress/ingress_edge_decision.hpp b/src/graphlab/graph/ingress/ingress_edge_decision.hpp index ef5e4d3c9c..093bb320a0 100644 --- a/src/graphlab/graph/ingress/ingress_edge_decision.hpp +++ b/src/graphlab/graph/ingress/ingress_edge_decision.hpp @@ -28,6 +28,7 @@ #include #include #include +#include namespace graphlab { template @@ -40,6 +41,8 @@ namespace graphlab { typedef graphlab::vertex_id_type vertex_id_type; typedef distributed_graph graph_type; typedef fixed_dense_bitset bin_counts_type; + typedef typename boost::unordered_map master_hash_table_type; + public: /** \brief A decision object for computing the edge assingment. */ @@ -66,6 +69,46 @@ namespace graphlab { return candidates[graph_hash::hash_edge(edge_pair) % (candidates.size())]; }; + /** Assign edges via a heuristic method called ginger */ + procid_t edge_to_proc_ginger ( + const vertex_id_type vid, + master_hash_table_type& mht, + master_hash_table_type& mht_incremental, + std::vector& proc_num_vertices, + double alpha, + double gamma, + std::vector& in) { + size_t numprocs = proc_num_vertices.size(); + + // Compute the score of each proc. + procid_t best_proc = -1; + double maxscore = 0.0; + std::vector proc_score(numprocs); + std::vector proc_degrees(numprocs); + + for (size_t i = 0; i < in.size(); ++i) { + if (mht.find(in[i]) != mht.end()) + proc_degrees[mht[in[i]]]++; + else if (mht_incremental.find(in[i]) != mht_incremental.end()) + proc_degrees[mht_incremental[in[i]]]++; + } + + for (size_t i = 0; i < numprocs; ++i) { + proc_score[i] = proc_degrees[i] - alpha * gamma * pow(proc_num_vertices[i], gamma-1); + } + maxscore = *std::max_element(proc_score.begin(), proc_score.end()); + + for (size_t i = 0; i < numprocs; ++i) { + if (proc_score[i] == maxscore) { + best_proc = i; + break; + } + } + + proc_num_vertices[best_proc]++; + + return best_proc; + }; /** Greedy assign (source, target) to a machine using: * bitset src_degree : the degree presence of source over machines diff --git a/src/graphlab/util/tetrad.hpp b/src/graphlab/util/tetrad.hpp new file mode 100755 index 0000000000..6decf7f7c0 --- /dev/null +++ b/src/graphlab/util/tetrad.hpp @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2013 Shanghai Jiao Tong University. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * author: rong chen (rongchen@sjtu.edu.cn) 2013.7 + * + */ + +#ifndef GRAPHLAB_TETRAD_HPP +#define GRAPHLAB_TETRAD_HPP + + +#include + +#include +#include + +namespace graphlab { + + template + struct tetrad { + typedef _T1 first_type; + typedef _T2 second_type; + typedef _T3 third_type; + typedef _T4 fourth_type; + + first_type first; + second_type second; + third_type third; + fourth_type fourth; + + tetrad() : first(_T1()), second(_T2()), third(_T3()), fourth(_T4()) {} + tetrad(const _T1& x, const _T2& y, const _T3& z, const _T4& w) : + first(x), second(y), third(z), fourth(w) {} + + tetrad(const tetrad<_T1, _T2, _T3, _T4>& o) : + first(o.first), second(o.second), third(o.third), fourth(o.fourth){} + + void load(iarchive& iarc) { + iarc >> first; + iarc >> second; + iarc >> third; + iarc >> fourth; + } + + void save(oarchive& oarc) const { + oarc << first; + oarc << second; + oarc << third; + oarc << fourth; + } + }; + + template + inline bool operator == (const tetrad<_T1, _T2, _T3, _T4>& x, + const tetrad<_T1, _T2, _T3, _T4>& y) { + return x.first == y.first && x.second == y.second + && x.third == y.third && x.fourth == y.fourth; + } + + template + inline bool operator < (const tetrad<_T1, _T2, _T3, _T4>& l, + const tetrad<_T1, _T2, _T3, _T4>& r) { + return (l.first < r.first) || + (!(r.first < l.first) + && (l.second < r.second)) || + (!(r.first < l.first) + && !(r.second < l.second) + && (l.third < r.third)) || + (!(r.first < l.first) + && !(r.second < l.second) + && !(r.third < l.third) + && (l.fourth< r.fourth)); + + } + + template + inline bool operator != (const tetrad<_T1, _T2, _T3, _T4>& l, + const tetrad<_T1, _T2, _T3, _T4>& r) { + return !(l == r); + } + + template + inline bool operator > (const tetrad<_T1, _T2, _T3, _T4>& l, + const tetrad<_T1, _T2, _T3, _T4>& r) { + return r < l; + } + + template + inline bool operator <= (const tetrad<_T1, _T2, _T3, _T4>& l, + const tetrad<_T1, _T2, _T3, _T4>& r) { + return !(r < l); + } + + template + inline bool operator >= (const tetrad<_T1, _T2, _T3, _T4>& l, + const tetrad<_T1, _T2, _T3, _T4>& r) { + return !(l < r); + } + + template + inline tetrad<_T1, _T2, _T3, _T4> make_tetrad( + const _T1& x, const _T2& y, const _T3& z, const _T4& w) { + return tetrad<_T1, _T2, _T3, _T4>(x, y, z, w); + } +}; // end of graphlab namespace + +#endif + + diff --git a/src/graphlab/util/triple.hpp b/src/graphlab/util/triple.hpp new file mode 100755 index 0000000000..e1f324a37c --- /dev/null +++ b/src/graphlab/util/triple.hpp @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2013 Shanghai Jiao Tong University. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * author: rong chen (rongchen@sjtu.edu.cn) 2013.7 + * + */ + +#ifndef GRAPHLAB_TRIPLE_HPP +#define GRAPHLAB_TRIPLE_HPP + + +#include + +#include +#include + +namespace graphlab { + + template + struct triple { + typedef _T1 first_type; + typedef _T2 second_type; + typedef _T3 third_type; + + first_type first; + second_type second; + third_type third; + + triple() : first(_T1()), second(_T2()), third(_T3()) {} + triple(const _T1& x, const _T2& y, const _T3& z) : + first(x), second(y), third(z) {} + + triple(const triple<_T1, _T2, _T3>& o) : + first(o.first), second(o.second), third(o.third){} + + void load(iarchive& iarc) { + iarc >> first; + iarc >> second; + iarc >> third; + } + + void save(oarchive& oarc) const { + oarc << first; + oarc << second; + oarc << third; + } + }; + + template + inline bool operator == (const triple<_T1, _T2, _T3>& x, + const triple<_T1, _T2, _T3>& y) { + return x.first == y.first && x.second == y.second && x.third == y.third; + } + + template + inline bool operator < (const triple<_T1, _T2, _T3>& l, + const triple<_T1, _T2, _T3>& r) { + return (l.first < r.first) || + (!(r.first < l.first) + && (l.second < r.second)) || + (!(r.first < l.first) + && !(r.second < l.second) + && (l.third < r.third)); + + } + + template + inline bool operator != (const triple<_T1, _T2, _T3>& l, + const triple<_T1, _T2, _T3>& r) { + return !(l == r); + } + + template + inline bool operator > (const triple<_T1, _T2, _T3>& l, + const triple<_T1, _T2, _T3>& r) { + return r < l; + } + + template + inline bool operator <= (const triple<_T1, _T2, _T3>& l, + const triple<_T1, _T2, _T3>& r) { + return !(r < l); + } + + template + inline bool operator >= (const triple<_T1, _T2, _T3>& l, + const triple<_T1, _T2, _T3>& r) { + return !(l < r); + } + + template + inline triple<_T1, _T2, _T3> make_triple( + const _T1& x, const _T2& y, const _T3& z) { + return triple<_T1, _T2, _T3>(x, y, z); + } +}; // end of graphlab namespace + +#endif + From 5b7aac4c60aba9964742709e147690ac7728fddc Mon Sep 17 00:00:00 2001 From: Yanzhe Chen Date: Fri, 1 Nov 2013 10:48:08 +0800 Subject: [PATCH 02/50] support add vertex for hybrid ginger --- .../distributed_hybrid_ginger_ingress.hpp | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp index cbd8bd19ee..b67f529686 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp @@ -121,6 +121,7 @@ namespace graphlab { /** Number of edges and vertices in graph. */ buffered_exchange hybrid_edge_exchange; buffered_exchange hybrid_vertex_exchange; + buffered_exchange temporary_vertex_exchange; /** master hash table: * mht is the synchronized one across the cluster, @@ -166,7 +167,7 @@ namespace graphlab { size_t threshold = 100, size_t nedges = 0, size_t nverts = 0, size_t interval = std::numeric_limits::max()) : base_type(dc, graph), - hybrid_edge_exchange(dc), hybrid_vertex_exchange(dc), + hybrid_edge_exchange(dc), hybrid_vertex_exchange(dc),temporary_vertex_exchange(dc), master_exchange(dc), hybrid_rpc(dc, this),graph(graph),high_edge_exchange(dc),low_edge_exchange(dc),proc_edges_exchange(dc), proc_edges_incremental(dc.numprocs()),proc_num_vertices(dc.numprocs()), threshold(threshold), @@ -273,7 +274,7 @@ namespace graphlab { const procid_t owning_proc = graph_hash::hash_vertex(vid) % hybrid_rpc.numprocs(); const vertex_buffer_record record(vid, vdata); - hybrid_vertex_exchange.send(owning_proc, record); + temporary_vertex_exchange.send(owning_proc, record); } // end of add vertex @@ -502,7 +503,7 @@ namespace graphlab { /* flush any additional data */ /* */ /**************************************************************************/ - hybrid_vertex_exchange.flush(); + temporary_vertex_exchange.flush(); /**************************************************************************/ @@ -583,6 +584,19 @@ namespace graphlab { /**************************************************************************/ // Setup the map containing all the vertices being negotiated by this machine { + if (temporary_vertex_exchange.size() > 0) { + vertex_buffer_type vertex_buffer; procid_t sending_proc(-1); + while(temporary_vertex_exchange.recv(sending_proc, vertex_buffer)) { + foreach(const vertex_buffer_record& rec, vertex_buffer) { + if (mht.find(rec.vid) == mht.end()) + mht[rec.vid] = graph_hash::hash_vertex(rec.vid) % hybrid_rpc.numprocs(); + hybrid_vertex_exchange.send(mht[rec.vid], rec); + } + } + temporary_vertex_exchange.clear(); + } + hybrid_vertex_exchange.flush(); + // receive any vertex data sent by other machines if (hybrid_vertex_exchange.size() > 0) { vertex_buffer_type vertex_buffer; procid_t sending_proc(-1); From c3e88dfb051e396002eda935da225a25bbb12159 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Thu, 14 Nov 2013 11:19:54 +0800 Subject: [PATCH 03/50] modify license note only add new apache lisence with ipads.sjtu to new add files is it right ... --- src/graphlab/engine/powerlyra_sync_engine.hpp | 5 ++--- .../ingress/distributed_hybrid_ginger_ingress.hpp | 10 ++++++---- .../graph/ingress/distributed_hybrid_ingress.hpp | 11 +++++++---- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/graphlab/engine/powerlyra_sync_engine.hpp b/src/graphlab/engine/powerlyra_sync_engine.hpp index 0e3b5f1607..68fd7a3697 100644 --- a/src/graphlab/engine/powerlyra_sync_engine.hpp +++ b/src/graphlab/engine/powerlyra_sync_engine.hpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2009 Carnegie Mellon University. + * Copyright (c) 2013 Institute of Parallel and Distributed Systems, Shanghai Jiao Tong University. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,12 +16,11 @@ * * For more about this software visit: * - * http://www.graphlab.ml.cmu.edu + * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html * */ - #ifndef GRAPHLAB_POWERLYRA_SYNC_ENGINE_HPP #define GRAPHLAB_POWERLYRA_SYNC_ENGINE_HPP diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp index b67f529686..57e4f6e6c2 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2009 Carnegie Mellon University. +/** + * Copyright (c) 2013 Institute of Parallel and Distributed Systems, Shanghai Jiao Tong University. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -9,16 +9,18 @@ * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an "AS * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language * governing permissions and limitations under the License. * * For more about this software visit: * - * http://www.graphlab.ml.cmu.edu + * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html * */ + #ifndef GRAPHLAB_DISTRIBUTED_HYBRID_GINGER_INGRESS_HPP #define GRAPHLAB_DISTRIBUTED_HYBRID_GINGER_INGRESS_HPP diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp index 82946f7d6f..a129276698 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2009 Carnegie Mellon University. +/** + * Copyright (c) 2013 Institute of Parallel and Distributed Systems, Shanghai Jiao Tong University. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -9,16 +9,19 @@ * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an "AS * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language * governing permissions and limitations under the License. * * For more about this software visit: * - * http://www.graphlab.ml.cmu.edu + * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html * */ + + #ifndef GRAPHLAB_DISTRIBUTED_HYBRID_INGRESS_HPP #define GRAPHLAB_DISTRIBUTED_HYBRID_INGRESS_HPP From 109141937eebbdb353407a0f4f38790234f922f1 Mon Sep 17 00:00:00 2001 From: Yanzhe Chen Date: Thu, 14 Nov 2013 13:55:57 +0800 Subject: [PATCH 04/50] make svd support more engine options --- toolkits/collaborative_filtering/svd.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/toolkits/collaborative_filtering/svd.cpp b/toolkits/collaborative_filtering/svd.cpp index 7fb4ea6721..24509514aa 100644 --- a/toolkits/collaborative_filtering/svd.cpp +++ b/toolkits/collaborative_filtering/svd.cpp @@ -603,7 +603,7 @@ int main(int argc, char** argv) { "Compute the gklanczos factorization of a matrix."; graphlab::command_line_options clopts(description); std::string input_dir, output_dir; - std::string exec_type = "synchronous"; + // std::string exec_type = "synchronous"; clopts.attach_option("matrix", input_dir, "The directory containing the matrix file"); clopts.add_positional("matrix"); @@ -626,6 +626,7 @@ int main(int argc, char** argv) { clopts.attach_option("predictions", predictions, "predictions file prefix"); clopts.attach_option("binary", binary, "If true, all edges are weighted as one"); clopts.attach_option("input_file_offset", input_file_offset, "input file node id offset (default 0)"); + clopts.attach_option("engine", exec_type, "specify engine type"); if(!clopts.parse(argc, argv) || input_dir == "") { std::cout << "Error in parsing command line arguments." << std::endl; clopts.print_description(); From fdcd42955057118c78229bbe2636ab00df39d455 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Thu, 14 Nov 2013 17:07:34 +0800 Subject: [PATCH 05/50] oops --- toolkits/collaborative_filtering/svd.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/toolkits/collaborative_filtering/svd.cpp b/toolkits/collaborative_filtering/svd.cpp index 24509514aa..42bb824afe 100644 --- a/toolkits/collaborative_filtering/svd.cpp +++ b/toolkits/collaborative_filtering/svd.cpp @@ -69,6 +69,7 @@ bool binary = false; //if true, all edges = 1 mat a,PT; bool v_vector = false; int input_file_offset = 0; //if set to non zero, each row/col id will be reduced the input_file_offset +std::string exec_type = "synchronous"; DECLARE_TRACER(svd_bidiagonal); DECLARE_TRACER(svd_error_estimate); From 1fbc9d1c765176b00feffd220386541665eafd76 Mon Sep 17 00:00:00 2001 From: Yanzhe Chen Date: Fri, 15 Nov 2013 13:29:36 +0800 Subject: [PATCH 06/50] fix empty finalize overhead --- .../ingress/distributed_hybrid_ingress.hpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp index a129276698..d173a26916 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp @@ -461,6 +461,15 @@ namespace graphlab { /* */ /**************************************************************************/ hybrid_vertex_exchange.flush(); + + { + size_t changed_size = nedges + hybrid_vertex_exchange.size(); + hybrid_rpc.all_reduce(changed_size); + if (changed_size == 0) { + logstream(LOG_INFO) << "Skipping Graph Finalization because no changes happened..." << std::endl; + return; + } + } /**************************************************************************/ @@ -745,12 +754,14 @@ namespace graphlab { if (!first_time_finalize) { vertex_set changed_vset = vertex_set(false); changed_vset.make_explicit(graph); + updated_lvids.resize(graph.num_local_vertices()); for (lvid_type i = lvid_start; i < graph.num_local_vertices(); ++i) { updated_lvids.set_bit(i); } changed_vset.localvset = updated_lvids; buffered_exchange vset_exchange(hybrid_rpc.dc()); + // sync vset with all mirrors changed_vset.synchronize_mirrors_to_master_or(graph, vset_exchange); changed_vset.synchronize_master_to_mirrors(graph, vset_exchange); @@ -761,7 +772,7 @@ namespace graphlab { boost::bind(&distributed_hybrid_ingress::finalize_gather, this, _1, _2), boost::bind(&distributed_hybrid_ingress::finalize_apply, this, _1, _2, _3)); vrecord_sync_gas.exec(changed_vset); -#ifdef TUNING + if(l_procid == 0) { memory_info::log_usage("synchronizing vertex (meta)data done."); logstream(LOG_EMPH) << "synchrionizing vertex (meta)data: " @@ -769,11 +780,10 @@ namespace graphlab { << " secs" << std::endl; } -#endif } base_type::exchange_global_info(); -#ifdef TUNING + if(l_procid == 0) { memory_info::log_usage("exchange global info done."); logstream(LOG_EMPH) << "exchange global info: " @@ -781,7 +791,6 @@ namespace graphlab { << " secs" << std::endl; } -#endif } // end of modified base finalize private: From e04d91a1572bbe357333ec0bd27b61df50ddf6ff Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Sat, 16 Nov 2013 22:51:59 +0800 Subject: [PATCH 07/50] optimize finalizing for standalone environment and redundant calling --- .../engine/async_consistent_engine.hpp | 2 +- .../ingress/distributed_hybrid_ingress.hpp | 552 ++++++++++-------- toolkits/graph_analytics/pagerank.cpp | 10 +- 3 files changed, 324 insertions(+), 240 deletions(-) diff --git a/src/graphlab/engine/async_consistent_engine.hpp b/src/graphlab/engine/async_consistent_engine.hpp index acf9ea1587..17a8cbb1cf 100644 --- a/src/graphlab/engine/async_consistent_engine.hpp +++ b/src/graphlab/engine/async_consistent_engine.hpp @@ -1012,7 +1012,7 @@ namespace graphlab { const typename graph_type::vertex_record& rec = graph.l_get_vertex_record(lvid); vertex_id_type vid = rec.gvid; char task_time_data[sizeof(timer)]; - timer* task_time; + timer* task_time = NULL; if (track_task_time) { // placement new to create the timer task_time = reinterpret_cast(task_time_data); diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp index d173a26916..ac5694309b 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp @@ -64,10 +64,6 @@ namespace graphlab { typedef typename graph_type::vertex_record vertex_record; typedef typename graph_type::mirror_type mirror_type; - - typedef typename hopscotch_map::value_type - vid2lvid_pair_type; - typedef typename buffered_exchange::buffer_type vertex_id_buffer_type; @@ -77,19 +73,25 @@ namespace graphlab { dc_dist_object hybrid_rpc; /// The underlying distributed graph object that is being loaded graph_type& graph; + + /// threshold to divide high-degree and low-degree vertices + size_t threshold; + bool standalone; - /// one-by-one ingress. e.g., SNAP typedef typename base_type::edge_buffer_record edge_buffer_record; typedef typename buffered_exchange::buffer_type - edge_buffer_type; - - buffered_exchange hybrid_edge_exchange; + edge_buffer_type; typedef typename base_type::vertex_buffer_record vertex_buffer_record; typedef typename buffered_exchange::buffer_type vertex_buffer_type; - + + std::vector hybrid_edges; + + + /// one-by-one ingress. e.g., SNAP + buffered_exchange hybrid_edge_exchange; buffered_exchange hybrid_vertex_exchange; /// batch ingress. e.g., R-ADJ @@ -118,17 +120,15 @@ namespace graphlab { typedef typename base_type::vertex_negotiator_record vertex_negotiator_record; - - /// threshold to divide high-degree and low-degree vertices - size_t threshold; - public: distributed_hybrid_ingress(distributed_control& dc, - graph_type& graph, size_t threshold = 100) : - base_type(dc, graph), hybrid_rpc(dc, this), graph(graph), - hybrid_edge_exchange(dc), hybrid_vertex_exchange(dc), - high_batch_edge_exchange(dc), low_batch_edge_exchange(dc), - threshold(threshold) { + graph_type& graph, size_t threshold = 100) : + base_type(dc, graph), hybrid_rpc(dc, this), + graph(graph), threshold(threshold), + hybrid_edge_exchange(dc), hybrid_vertex_exchange(dc), + high_batch_edge_exchange(dc), low_batch_edge_exchange(dc){ + /* fast pass for standalone case. */ + standalone = hybrid_rpc.numprocs() == 1; hybrid_rpc.barrier(); } // end of constructor @@ -140,10 +140,15 @@ namespace graphlab { */ void add_edge(vertex_id_type source, vertex_id_type target, const EdgeData& edata) { - const procid_t owning_proc = - graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); const edge_buffer_record record(source, target, edata); - hybrid_edge_exchange.send(owning_proc, record); + if (standalone) { + /* Fast pass for standalone case. */ + hybrid_edges.push_back(record); + } else { + const procid_t owning_proc = + graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); + hybrid_edge_exchange.send(owning_proc, record); + } } // end of add edge @@ -154,38 +159,51 @@ namespace graphlab { */ void add_edges(std::vector& sources, vertex_id_type target, const std::vector& edatas) { - const procid_t target_owner_proc = - graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); - - if(sources.size()>=threshold){ - std::vector batch_rec_vector(hybrid_rpc.numprocs()); - - for(size_t i = 0; i < sources.size(); i++){ - const procid_t source_owner_proc = - graph_hash::hash_vertex(sources[i]) % hybrid_rpc.numprocs(); - batch_rec_vector[source_owner_proc].sources.push_back(sources[i]); - batch_rec_vector[source_owner_proc].edatas.push_back(edatas[i]); - } - - for(size_t i = 0; i < batch_rec_vector.size(); i++){ - if(batch_rec_vector[i].sources.size() > 0){ - batch_rec_vector[i].target=target; - high_batch_edge_exchange.send((procid_t) i, batch_rec_vector[i]); + if (standalone) { + /* fast pass for standalone case. */ + for(size_t i = 0; i < sources.size();i++){ + const edge_buffer_record record(sources[i], target, edatas[i]); + hybrid_edges.push_back(record); + } + } else { + const procid_t target_owner_proc = + graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); + + if(sources.size() >= threshold){ + std::vector batch_rec_vector(hybrid_rpc.numprocs()); + + for (size_t i = 0; i < sources.size(); i++){ + const procid_t source_owner_proc = + graph_hash::hash_vertex(sources[i]) % hybrid_rpc.numprocs(); + batch_rec_vector[source_owner_proc].sources.push_back(sources[i]); + batch_rec_vector[source_owner_proc].edatas.push_back(edatas[i]); + } + + for (size_t i = 0; i < batch_rec_vector.size(); i++) { + if(batch_rec_vector[i].sources.size() > 0){ + batch_rec_vector[i].target=target; + high_batch_edge_exchange.send((procid_t)i, batch_rec_vector[i]); + } } } - } - else{ - const batch_edge_buffer_record record(sources, target, edatas); - low_batch_edge_exchange.send(target_owner_proc, record); + else{ + const batch_edge_buffer_record record(sources, target, edatas); + low_batch_edge_exchange.send(target_owner_proc, record); + } } } // end of add edges /* add vdata */ - void add_vertex(vertex_id_type vid, const VertexData& vdata) { - const procid_t owning_proc = - graph_hash::hash_vertex(vid) % hybrid_rpc.numprocs(); + void add_vertex(vertex_id_type vid, const VertexData& vdata) { const vertex_buffer_record record(vid, vdata); - hybrid_vertex_exchange.send(owning_proc, record); + if (standalone) { + /* fast pass for redundant finalization with no graph changes. */ + hybrid_vertex_exchange.send(0, record); + } else { + const procid_t owning_proc = + graph_hash::hash_vertex(vid) % hybrid_rpc.numprocs(); + hybrid_vertex_exchange.send(owning_proc, record); + } } // end of add vertex @@ -195,14 +213,11 @@ namespace graphlab { size_t nprocs = hybrid_rpc.numprocs(); procid_t l_procid = hybrid_rpc.procid(); - - std::vector hybrid_edges; - hopscotch_map in_degree_set; - size_t nedges; + size_t nedges = 0; if (l_procid == 0) { memory_info::log_usage("start finalizing"); - logstream(LOG_EMPH) << "hybrid orignal finalizing Graph ..." + logstream(LOG_EMPH) << "hybrid finalizing ..." << " #vertices=" << graph.local_graph.num_vertices() << " #edges=" << graph.local_graph.num_edges() << " threshold=" << threshold @@ -211,216 +226,272 @@ namespace graphlab { /**************************************************************************/ /* */ - /* one-by-one ingress */ + /* prepare hybrid ingress */ /* */ /**************************************************************************/ - /* This is an extra phase for SNAP graph. - * In this phase, we edges in hybrid_edge_exchange buffer to hybrid_edges - * and construct the in_degree_set at the same time. - */ { - hybrid_edge_exchange.flush(); - nedges = hybrid_edge_exchange.size(); - hybrid_edges.reserve(nedges); -#ifdef TUNING - logstream(LOG_INFO) << "#" << l_procid << " receive " - << nedges - << " edge-msgs" - << std::endl; - - if (l_procid == 0) { - memory_info::log_usage("edge_exchange flush done."); - logstream(LOG_EMPH) << "edge_exchange flush: " - << ti.current_time() - << " secs" - << std::endl; - } -#endif - edge_buffer_type edge_buffer; - procid_t proc = -1; - while(hybrid_edge_exchange.recv(proc, edge_buffer)) { - foreach(const edge_buffer_record& rec, edge_buffer) { - hybrid_edges.push_back(rec); - in_degree_set[rec.target]++; - } - } - hybrid_edge_exchange.clear(); - // sync before reusing - hybrid_edge_exchange.barrier(); + if (standalone) { + nedges = hybrid_edges.size(); + } else { + hopscotch_map in_degree_set; + edge_buffer_type edge_buffer; + procid_t proc; + + /* collect edges for one-by-one ingress (e.g. SNAP) */ + hybrid_edge_exchange.flush(); + if (hybrid_edge_exchange.size() > 0) { + nedges = hybrid_edge_exchange.size(); + hybrid_edges.reserve(nedges); + + proc = -1; + while(hybrid_edge_exchange.recv(proc, edge_buffer)) { + foreach(const edge_buffer_record& rec, edge_buffer) { + hybrid_edges.push_back(rec); + in_degree_set[rec.target]++; + } + } + hybrid_edge_exchange.clear(); + hybrid_edge_exchange.barrier(); // sync before reusing #ifdef TUNING - if(l_procid == 0) { - memory_info::log_usage("save local edges and count in-degree."); - logstream(LOG_EMPH) << "save local edges and count in-degree: " - << ti.current_time() - << " secs" - << std::endl; - } + if(l_procid == 0) { + memory_info::log_usage("save local edges and count in-degree done."); + logstream(LOG_EMPH) << "save local edges and count in-degree: " + << ti.current_time() + << " secs" + << std::endl; + } #endif - // classify and record vertices - for (size_t i = 0; i < hybrid_edges.size(); i++) { - edge_buffer_record& rec = hybrid_edges[i]; - const procid_t source_owner_proc = - graph_hash::hash_vertex(rec.source) % nprocs; - - // high-degree target vertex - if (in_degree_set[rec.target] >= threshold) { - if(source_owner_proc != l_procid){ - // re-send the edge of high-degree vertices according to source - hybrid_edge_exchange.send(source_owner_proc, rec); - // set re-sent edges as empty for skipping - hybrid_edges[i] = edge_buffer_record(); - --nedges; + // re-send edges of high-degree vertices + for (size_t i = 0; i < hybrid_edges.size(); i++) { + edge_buffer_record& rec = hybrid_edges[i]; + if (in_degree_set[rec.target] >= threshold) { + const procid_t source_owner_proc = + graph_hash::hash_vertex(rec.source) % nprocs; + if(source_owner_proc != l_procid){ + // re-send the edge of high-degree vertices according to source + hybrid_edge_exchange.send(source_owner_proc, rec); + // set re-sent edges as empty for skipping + hybrid_edges[i] = edge_buffer_record(); + --nedges; + } + } } - } - } -#ifdef TUNING - if(l_procid == 0) { - memory_info::log_usage("store vertex and resend done."); - logstream(LOG_EMPH) << "store vertex and resend: " - << ti.current_time() - << " secs" - << std::endl; - } -#endif - // receive re-sent edges and high-degree mirrors - hybrid_edge_exchange.flush(); -#ifdef TUNING - if(l_procid == 0) - logstream(LOG_INFO) << "receive high-degree edges: " - << hybrid_edge_exchange.size() << std::endl; -#endif - proc = -1; - while(hybrid_edge_exchange.recv(proc, edge_buffer)) { - foreach(const edge_buffer_record& rec, edge_buffer) { - hybrid_edges.push_back(rec); - ++nedges; - } - } - hybrid_edge_exchange.clear(); - in_degree_set.clear(); #ifdef TUNING - if(l_procid == 0) { - memory_info::log_usage("receive high-degree mirrors done."); - logstream(LOG_EMPH) << "receive high-degree mirrors: " - << ti.current_time() - << " secs" - << std::endl; - } + if(l_procid == 0) { + memory_info::log_usage("resend edges of high-degree vertices done."); + logstream(LOG_EMPH) << "resend edges of high-degree vertices: " + << ti.current_time() + << " secs" + << std::endl; + } #endif - } - - /**************************************************************************/ - /* */ - /* batch ingress */ - /* */ - /**************************************************************************/ - /* This is an extra phase for RADJ graph. - * In this phase, we move edges from low_batch and high_batch buffers into - * hybrid_edges - */ - { - low_batch_edge_exchange.flush(); - if (low_batch_edge_exchange.size() > 0) { + // receive edges of high-degree vertices + hybrid_edge_exchange.flush(); #ifdef TUNING - logstream(LOG_INFO) << "#" << l_procid << " receive " - << low_batch_edge_exchange.size() - << " low-batch-edge-msgs" - << std::endl; + if(l_procid == 0) + logstream(LOG_INFO) << "receive high-degree edges: " + << hybrid_edge_exchange.size() << std::endl; #endif - batch_edge_buffer_type batch_edge_buffer; - procid_t proc = -1; - while(low_batch_edge_exchange.recv(proc, batch_edge_buffer)) { - foreach(const batch_edge_buffer_record& batch_rec, batch_edge_buffer) { - nedges += batch_rec.sources.size(); - for(size_t i = 0; i < batch_rec.sources.size();i++){ - edge_buffer_record rec(batch_rec.sources[i], - batch_rec.target, - batch_rec.edatas[i]); + proc = -1; + while(hybrid_edge_exchange.recv(proc, edge_buffer)) { + foreach(const edge_buffer_record& rec, edge_buffer) { hybrid_edges.push_back(rec); + ++nedges; } } + hybrid_edge_exchange.clear(); + in_degree_set.clear(); +#ifdef TUNING + if(l_procid == 0) { + memory_info::log_usage("receive high-degree edges done."); + logstream(LOG_EMPH) << "receive high-degree edges: " + << ti.current_time() + << " secs" + << std::endl; + } +#endif } - low_batch_edge_exchange.clear(); - } - high_batch_edge_exchange.flush(); - if (high_batch_edge_exchange.size() > 0) { + /* collect edges for batch ingress (e.g. RADJ) */ + // store edges of low-degree vertices into hybrid_edges + low_batch_edge_exchange.flush(); + if (low_batch_edge_exchange.size() > 0) { + batch_edge_buffer_type batch_edge_buffer; + proc = -1; + while(low_batch_edge_exchange.recv(proc, batch_edge_buffer)) { + foreach(const batch_edge_buffer_record& batch_rec, batch_edge_buffer) { + nedges += batch_rec.sources.size(); + for(size_t i = 0; i < batch_rec.sources.size();i++){ + edge_buffer_record rec(batch_rec.sources[i], + batch_rec.target, + batch_rec.edatas[i]); + hybrid_edges.push_back(rec); + } + } + } + low_batch_edge_exchange.clear(); #ifdef TUNING - logstream(LOG_INFO) << "#" << l_procid << " receive " - << high_batch_edge_exchange.size() - << " high-batch-edge-msgs " - << std::endl; + if(l_procid == 0) { + memory_info::log_usage("receive low-degree edges done."); + logstream(LOG_EMPH) << "receive low-degree edges: " + << ti.current_time() + << " secs" + << std::endl; + } #endif - batch_edge_buffer_type batch_edge_buffer; - procid_t proc = -1; - while(high_batch_edge_exchange.recv(proc, batch_edge_buffer)) { - foreach(const batch_edge_buffer_record& batch_rec, batch_edge_buffer) { - nedges += batch_rec.sources.size(); - for(size_t i = 0; i < batch_rec.sources.size(); i++){ - edge_buffer_record rec(batch_rec.sources[i], - batch_rec.target, - batch_rec.edatas[i]); - hybrid_edges.push_back(rec); + + } + + // store edges of high-degree vertices into hybrid_edges + high_batch_edge_exchange.flush(); + if (high_batch_edge_exchange.size() > 0) { + batch_edge_buffer_type batch_edge_buffer; + procid_t proc = -1; + while(high_batch_edge_exchange.recv(proc, batch_edge_buffer)) { + foreach(const batch_edge_buffer_record& batch_rec, batch_edge_buffer) { + nedges += batch_rec.sources.size(); + for(size_t i = 0; i < batch_rec.sources.size(); i++){ + edge_buffer_record rec(batch_rec.sources[i], + batch_rec.target, + batch_rec.edatas[i]); + hybrid_edges.push_back(rec); + } } } + high_batch_edge_exchange.clear(); +#ifdef TUNING + if(l_procid == 0) { + memory_info::log_usage("receive high-degree edges done."); + logstream(LOG_EMPH) << "receive high-degree edges: " + << ti.current_time() + << " secs" + << std::endl; + } +#endif } - high_batch_edge_exchange.clear(); } } + if(l_procid == 0) { + memory_info::log_usage("prepare hybrid finalizing done."); + logstream(LOG_EMPH) << "prepare hybrid finalizing. (" + << ti.current_time() + << " secs)" + << std::endl; + } + // connect to base finalize() - // pass nedges and hybrid_edges to the base finalize - modified_base_finalize(nedges, hybrid_edges); - + modified_base_finalize(nedges); + if(l_procid == 0) { + memory_info::log_usage("base finalizing done."); + logstream(LOG_EMPH) << "base finalizing. (" + << ti.current_time() + << " secs)" + << std::endl; + } + set_vertex_type(); -#ifdef TUNING - if(l_procid == 0) - logstream(LOG_EMPH) << "orignal hybrid finalizing graph done. (" + if(l_procid == 0) { + memory_info::log_usage("set vertex type done."); + logstream(LOG_EMPH) << "set vertex type. (" << ti.current_time() << " secs)" << std::endl; -#endif + } + + if(l_procid == 0) { + memory_info::log_usage("hybrid finalizing graph done."); + logstream(LOG_EMPH) << "hybrid finalizing graph. (" + << ti.current_time() + << " secs)" + << std::endl; + } } // end of finalize void set_vertex_type() { graphlab::timer ti; procid_t l_procid = hybrid_rpc.procid(); + size_t high_master = 0, high_mirror = 0, low_master = 0, low_mirror = 0; for (size_t lvid = 0; lvid < graph.num_local_vertices(); lvid++) { vertex_record& vrec = graph.lvid2record[lvid]; - if (vrec.owner == l_procid) { - if (vrec.num_in_edges >= threshold) - vrec.type = graph_type::HIGH_MASTER; - else vrec.type = graph_type::LOW_MASTER; - } else { - if (vrec.num_in_edges >= threshold) + if (vrec.num_in_edges >= threshold) { + if (vrec.owner == l_procid) { + vrec.type = graph_type::HIGH_MASTER; + high_master ++; + } else { vrec.type = graph_type::HIGH_MIRROR; - else vrec.type = graph_type::LOW_MIRROR; - } + high_mirror ++; + } + } else { + if (vrec.owner == l_procid) { + vrec.type = graph_type::LOW_MASTER; + low_master ++; + } else { + vrec.type = graph_type::LOW_MIRROR; + low_mirror ++; + } + } } -#ifdef TUNING + +//#ifdef TUNING + // Compute the total number of high-degree and low-degree vertices + std::vector swap_counts(hybrid_rpc.numprocs()); + + swap_counts[l_procid] = high_master; + hybrid_rpc.all_gather(swap_counts); + high_master = 0; + foreach(size_t count, swap_counts) high_master += count; + + swap_counts[l_procid] = high_mirror; + hybrid_rpc.all_gather(swap_counts); + high_mirror = 0; + foreach(size_t count, swap_counts) high_mirror += count; + + swap_counts[l_procid] = low_master; + hybrid_rpc.all_gather(swap_counts); + low_master = 0; + foreach(size_t count, swap_counts) low_master += count; + + swap_counts[l_procid] = low_mirror; + hybrid_rpc.all_gather(swap_counts); + low_mirror = 0; + foreach(size_t count, swap_counts) low_mirror += count; + if(l_procid == 0) { + logstream(LOG_EMPH) << "hybrid info: master [" + << high_master << " " + << low_master << " " + << ((high_master*1.0)/(high_master+low_master)) << "]" + << std::endl; + if ((high_mirror + low_mirror) > 0) + logstream(LOG_EMPH) << "hybrid info: mirror [" + << high_mirror << " " + << low_mirror << " " + << ((high_mirror*1.0)/(high_mirror+low_mirror)) << "]" + << std::endl; + memory_info::log_usage("set vertex type done."); logstream(LOG_EMPH) << "set vertex type: " << ti.current_time() << " secs" << std::endl; } -#endif +//#endif } - /* do the same job as original base finalize except for + + /* + * do the same job as original base finalize except for * extracting edges from hybrid_edges instead of original edge_buffer */ - void modified_base_finalize(size_t nedges, - std::vector& hybrid_edges) { - + void modified_base_finalize(size_t nedges) { graphlab::timer ti; procid_t l_procid = hybrid_rpc.procid(); - + hybrid_rpc.full_barrier(); bool first_time_finalize = false; @@ -435,6 +506,10 @@ namespace graphlab { first_time_finalize = false; } + + typedef typename hopscotch_map::value_type + vid2lvid_pair_type; + /** * \internal * Buffer storage for new vertices to the local graph. @@ -457,11 +532,14 @@ namespace graphlab { /**************************************************************************/ /* */ - /* flush any additional data */ + /* Flush any additional data */ /* */ /**************************************************************************/ - hybrid_vertex_exchange.flush(); + hybrid_vertex_exchange.flush(); /* edges has stored in hybrid_edges */ + /** + * Fast pass for redundant finalization with no graph changes. + */ { size_t changed_size = nedges + hybrid_vertex_exchange.size(); hybrid_rpc.all_reduce(changed_size); @@ -519,8 +597,8 @@ namespace graphlab { graph.local_graph.num_vertices()); #ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("populating local graph done."); - logstream(LOG_EMPH) << "populating local graph: " + memory_info::log_usage("base::populating local graph done."); + logstream(LOG_EMPH) << "base::populating local graph: " << ti.current_time() << " secs" << std::endl; @@ -529,15 +607,15 @@ namespace graphlab { // Finalize local graph graph.local_graph.finalize(); #ifdef TUNING - logstream(LOG_INFO) << "local graph info: " << std::endl + logstream(LOG_INFO) << "base::local graph info: " << std::endl << "\t nverts: " << graph.local_graph.num_vertices() << std::endl << "\t nedges: " << graph.local_graph.num_edges() << std::endl; if(l_procid == 0) { - memory_info::log_usage("finalizing local graph done."); - logstream(LOG_EMPH) << "finalizing local graph: " + memory_info::log_usage("base::finalizing local graph done."); + logstream(LOG_EMPH) << "base::finalizing local graph: " << ti.current_time() << " secs" << std::endl; @@ -548,7 +626,7 @@ namespace graphlab { /**************************************************************************/ /* */ - /* receive and add vertex data to masters */ + /* Receive and add vertex data to masters */ /* */ /**************************************************************************/ // Setup the map containing all the vertices being negotiated by this machine @@ -581,11 +659,11 @@ namespace graphlab { } hybrid_vertex_exchange.clear(); #ifdef TUNING - logstream(LOG_INFO) << " #vert-msgs=" << hybrid_vertex_exchange.size() + logstream(LOG_INFO) << "base::#vert-msgs=" << hybrid_vertex_exchange.size() << std::endl; if(l_procid == 0) { - memory_info::log_usage("adding vertex data done."); - logstream(LOG_EMPH) << "adding vertex data: " + memory_info::log_usage("base::adding vertex data done."); + logstream(LOG_EMPH) << "base::adding vertex data: " << ti.current_time() << " secs" << std::endl; @@ -597,14 +675,9 @@ namespace graphlab { /**************************************************************************/ /* */ - /* assign vertex data and allocate vertex (meta)data space */ + /* Assign vertex data and allocate vertex (meta)data space */ /* */ /**************************************************************************/ - -#ifdef INGRESS_DEBUG - std::vector degree; - degree.resize(graph_type::NUM_ZONE_TYPES, 0); -#endif { // determine masters for all negotiated vertices const size_t local_nverts = graph.vid2lvid.size() + vid2lvid_buffer.size(); @@ -620,8 +693,8 @@ namespace graphlab { ASSERT_EQ(graph.lvid2record.size(), graph.local_graph.num_vertices()); #ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("allocating lvid2record done."); - logstream(LOG_EMPH) << "allocating lvid2record: " + memory_info::log_usage("base::allocating lvid2record done."); + logstream(LOG_EMPH) << "base::allocating lvid2record: " << ti.current_time() << " secs" << std::endl; @@ -629,9 +702,10 @@ namespace graphlab { #endif } + /**************************************************************************/ /* */ - /* master handshake */ + /* Master handshake */ /* */ /**************************************************************************/ { @@ -689,7 +763,7 @@ namespace graphlab { vid_buffer.clear(); if (!flying_vids.empty()) { - logstream(LOG_INFO) << "#flying-own-nverts=" + logstream(LOG_INFO) << "base::#flying-own-nverts=" << flying_vids.size() << std::endl; @@ -713,17 +787,18 @@ namespace graphlab { #ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("master handshake done."); - logstream(LOG_EMPH) << "master handshake: " + memory_info::log_usage("base::master handshake done."); + logstream(LOG_EMPH) << "base::master handshake: " << ti.current_time() << " secs" << std::endl; } #endif + /**************************************************************************/ /* */ - /* merge in vid2lvid_buffer */ + /* Merge in vid2lvid_buffer */ /* */ /**************************************************************************/ { @@ -741,7 +816,7 @@ namespace graphlab { /**************************************************************************/ /* */ - /* synchronize vertex data and meta information */ + /* Synchronize vertex data and meta information */ /* */ /**************************************************************************/ { @@ -761,7 +836,6 @@ namespace graphlab { } changed_vset.localvset = updated_lvids; buffered_exchange vset_exchange(hybrid_rpc.dc()); - // sync vset with all mirrors changed_vset.synchronize_mirrors_to_master_or(graph, vset_exchange); changed_vset.synchronize_master_to_mirrors(graph, vset_exchange); @@ -774,8 +848,8 @@ namespace graphlab { vrecord_sync_gas.exec(changed_vset); if(l_procid == 0) { - memory_info::log_usage("synchronizing vertex (meta)data done."); - logstream(LOG_EMPH) << "synchrionizing vertex (meta)data: " + memory_info::log_usage("base::synchronizing vertex (meta)data done."); + logstream(LOG_EMPH) << "base::synchrionizing vertex (meta)data: " << ti.current_time() << " secs" << std::endl; @@ -783,14 +857,16 @@ namespace graphlab { } base_type::exchange_global_info(); - + +#ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("exchange global info done."); - logstream(LOG_EMPH) << "exchange global info: " + memory_info::log_usage("base::exchange global info done."); + logstream(LOG_EMPH) << "base::exchange global info: " << ti.current_time() << " secs" << std::endl; } +#endif } // end of modified base finalize private: diff --git a/toolkits/graph_analytics/pagerank.cpp b/toolkits/graph_analytics/pagerank.cpp index 68f4efd2a6..377d97a6c8 100644 --- a/toolkits/graph_analytics/pagerank.cpp +++ b/toolkits/graph_analytics/pagerank.cpp @@ -217,12 +217,14 @@ int main(int argc, char** argv) { // make sure this is the synchronous engine dc.cout() << "--iterations set. Forcing Synchronous engine, and running " << "for " << ITERATIONS << " iterations." << std::endl; - clopts.get_engine_args().set_option("type", "synchronous"); + //clopts.get_engine_args().set_option("type", "synchronous"); clopts.get_engine_args().set_option("max_iterations", ITERATIONS); clopts.get_engine_args().set_option("sched_allv", true); } // Build the graph ---------------------------------------------------------- + dc.cout() << "Loading graph." << std::endl; + graphlab::timer timer; graph_type graph(dc, clopts); if(powerlaw > 0) { // make a synthetic graph dc.cout() << "Loading synthetic Powerlaw graph." << std::endl; @@ -237,8 +239,14 @@ int main(int argc, char** argv) { clopts.print_description(); return 0; } + dc.cout() << "Loading graph. Finished in " + << timer.current_time() << std::endl; // must call finalize before querying the graph + dc.cout() << "Finalizing graph." << std::endl; graph.finalize(); + dc.cout() << "Finalizing graph. Finished in " + << timer.current_time() << std::endl; + dc.cout() << "#vertices: " << graph.num_vertices() << " #edges:" << graph.num_edges() << std::endl; From 7789854505c38e03654219ab8e284504d78f1232 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Sun, 17 Nov 2013 11:11:25 +0800 Subject: [PATCH 08/50] support sched_allv --- src/graphlab/engine/powerlyra_sync_engine.hpp | 81 ++++++++++++------- src/graphlab/engine/synchronous_engine.hpp | 8 +- 2 files changed, 56 insertions(+), 33 deletions(-) diff --git a/src/graphlab/engine/powerlyra_sync_engine.hpp b/src/graphlab/engine/powerlyra_sync_engine.hpp index 68fd7a3697..a6aeea2ca5 100644 --- a/src/graphlab/engine/powerlyra_sync_engine.hpp +++ b/src/graphlab/engine/powerlyra_sync_engine.hpp @@ -60,6 +60,7 @@ #define ALIGN_DOWN(_n, _w) ((_n) & (~((_w)-1))) +#undef TUNING namespace graphlab { @@ -567,15 +568,14 @@ namespace graphlab { typedef powerlyra_sync_engine engine_type; /** - * \brief The triple type used to activate mirrors. + * \brief The pair type used to synchronize vertex programs across machines. */ - typedef triple - vid_vprog_edir_triple_type; + typedef std::pair vid_vprog_pair_type; /** * \brief The type of the express used to activate mirrors */ - typedef fiber_buffered_exchange + typedef fiber_buffered_exchange activ_exchange_type; /** @@ -1136,7 +1136,8 @@ namespace graphlab { // Graph should has been finalized ASSERT_TRUE(graph.is_finalized()); // Only support zone cuts - ASSERT_TRUE(graph.get_cuts_type() == graph_type::HYBRID_CUTS || graph.get_cuts_type() == graph_type::HYBRID_GINGER_CUTS); + ASSERT_TRUE(graph.get_cuts_type() == graph_type::HYBRID_CUTS + || graph.get_cuts_type() == graph_type::HYBRID_GINGER_CUTS); // if (rmi.procid() == 0) graph.dump_graph_info(); init(); @@ -1271,7 +1272,9 @@ namespace graphlab { void powerlyra_sync_engine:: internal_signal(const vertex_type& vertex) { const lvid_type lvid = vertex.local_id(); + // set an empty message messages[lvid] = message_type(); + // atomic set is enough, without acquiring and releasing lock has_message.set_bit(lvid); } // end of internal_signal @@ -1376,7 +1379,7 @@ namespace graphlab { start_time = timer::approx_time_seconds(); exec_time = exch_time = recv_time = gather_time = apply_time = scatter_time = 0.0; - graphlab::timer ti, bk_ti, iter_ti; + graphlab::timer ti, bk_ti; iteration_counter = 0; force_abort = false; execution_status::status_enum termination_reason = execution_status::UNSET; @@ -1405,8 +1408,6 @@ namespace graphlab { } bool print_this_round = (elapsed_seconds() - last_print) >= print_interval; - //debug - //print_this_round = true; if(rmi.procid() == 0 && print_this_round) { logstream(LOG_DEBUG) << rmi.procid() << ": Starting iteration: " << iteration_counter @@ -1466,19 +1467,13 @@ namespace graphlab { // Check termination condition --------------------------------------- size_t total_active_vertices = num_active_vertices; rmi.all_reduce(total_active_vertices); - if (rmi.procid() == 0 /*&& print_this_round*/) { + if (rmi.procid() == 0 && print_this_round) logstream(LOG_EMPH) - << iteration_counter << ":" - << "\tactive vertices = " << total_active_vertices - << "\ttime = " << iter_ti.current_time() - << "\tmsg = " << rmi.dc().network_bytes_sent() - << std::endl; - } + << "\tActive vertices: " << total_active_vertices << std::endl; if(total_active_vertices == 0 ) { termination_reason = execution_status::TASK_DEPLETION; break; } - iter_ti.start(); // Execute gather operations------------------------------------------- // 1. call pre_local_gather, gather and post_local_gather @@ -1553,7 +1548,6 @@ namespace graphlab { if (rmi.procid() == 0) { logstream(LOG_EMPH) << iteration_counter << " iterations completed." << std::endl; - memory_info::log_usage("Execution completed."); } // Final barrier to ensure that all engines terminate at the same time double total_compute_time = 0; @@ -1564,16 +1558,20 @@ namespace graphlab { all_compute_time_vec[rmi.procid()] = total_compute_time; rmi.all_gather(all_compute_time_vec); +#ifdef TUNING logstream(LOG_INFO) << "Local Calls(G|A|S): " << completed_gathers.value << "|" << completed_applys.value << "|" << completed_scatters.value << std::endl; +#endif size_t global_completed = completed_applys; rmi.all_reduce(global_completed); completed_applys = global_completed; + rmi.cout() << "Updates: " << completed_applys.value << "\n"; +#ifdef TUNING global_completed = completed_gathers; rmi.all_reduce(global_completed); completed_gathers = global_completed; @@ -1581,13 +1579,16 @@ namespace graphlab { global_completed = completed_scatters; rmi.all_reduce(global_completed); completed_scatters = global_completed; +#endif if (rmi.procid() == 0) { +#ifdef TUNING logstream(LOG_INFO) << "Total Calls(G|A|S): " << completed_gathers.value << "|" << completed_applys.value << "|" << completed_scatters.value << std::endl; +#endif logstream(LOG_INFO) << "Compute Balance: "; for (size_t i = 0;i < all_compute_time_vec.size(); ++i) { logstream(LOG_INFO) << all_compute_time_vec[i] << " "; @@ -1602,6 +1603,7 @@ namespace graphlab { << scatter_time << std::endl; } + rmi.full_barrier(); // Stop the aggregator aggregator.stop(); @@ -1637,6 +1639,8 @@ namespace graphlab { void powerlyra_sync_engine:: exchange_messages(const size_t thread_id) { context_type context(*this, graph); + const size_t TRY_RECV_MOD = 100; + size_t vcount = 0; fixed_dense_bitset<8 * sizeof(size_t)> local_bitset; // a word-size = 64 bit while (1) { @@ -1663,7 +1667,9 @@ namespace graphlab { has_message.clear_bit(lvid); // clear the message to save memory messages[lvid] = message_type(); + ++vcount; } + if(vcount % TRY_RECV_MOD == 0) recv_messages(); } } // end of loop over vertices to send messages message_exchange.partial_flush(); @@ -1679,6 +1685,8 @@ namespace graphlab { receive_messages(const size_t thread_id) { context_type context(*this, graph); fixed_dense_bitset<8 * sizeof(size_t)> local_bitset; // a word-size = 64 bit + const size_t TRY_RECV_MOD = 100; + size_t vcount = 0; size_t nactive_inc = 0; while (1) { @@ -1710,7 +1718,6 @@ namespace graphlab { // Determine if the gather should be run const vertex_program_type& const_vprog = vertex_programs[lvid]; edge_dirs[lvid] = const_vprog.gather_edges(context, vertex); - if(edge_dirs[lvid] != graphlab::NO_EDGES) { active_minorstep.set_bit(lvid); // send Gx1 msgs @@ -1719,9 +1726,11 @@ namespace graphlab { && ((edge_dirs[lvid] == graphlab::OUT_EDGES) || (edge_dirs[lvid] == graphlab::ALL_EDGES)))) { send_activs(lvid, thread_id); + ++vcount; } } } + if(vcount % TRY_RECV_MOD == 0) recv_activs(); } num_active_vertices += nactive_inc; activ_exchange.partial_flush(); @@ -1738,7 +1747,9 @@ namespace graphlab { execute_gathers(const size_t thread_id) { context_type context(*this, graph); const bool caching_enabled = !gather_cache.empty(); - fixed_dense_bitset<8 * sizeof(size_t)> local_bitset; // a word-size = 64 bit + fixed_dense_bitset<8 * sizeof(size_t)> local_bitset; // a word-size = 64 bit + const size_t TRY_RECV_MOD = 1000; + size_t vcount = 0; size_t ngather_inc = 0; timer ti; @@ -1758,6 +1769,8 @@ namespace graphlab { if (lvid >= graph.num_local_vertices()) break; // [TARGET]: High/Low-degree Masters, and High/Low-degree Mirrors + if (low_mirror_lvid(lvid)) continue; + bool accum_is_set = false; gather_type accum = gather_type(); // if caching is enabled and we have a cache entry then use @@ -1770,7 +1783,7 @@ namespace graphlab { const vertex_program_type& vprog = vertex_programs[lvid]; local_vertex_type local_vertex = graph.l_vertex(lvid); const vertex_type vertex(local_vertex); - const edge_dir_type gather_dir = edge_dirs[lvid]; + const edge_dir_type gather_dir = vprog.gather_edges(context, vertex); size_t edges_touched = 0; vprog.pre_local_gather(accum); @@ -1814,7 +1827,14 @@ namespace graphlab { } // If the accum contains a value for the gather - if (accum_is_set) send_accum(lvid, accum, thread_id); + if (accum_is_set) { send_accum(lvid, accum, thread_id); ++vcount; } + if(!graph.l_is_master(lvid)) { + // if this is not the master clear the vertex program + vertex_programs[lvid] = vertex_program_type(); + } + + // try to recv gathers if there are any in the buffer + if(vcount % TRY_RECV_MOD == 0) recv_accums(); } } // end of loop over vertices to compute gather accumulators completed_gathers += ngather_inc; @@ -1832,6 +1852,8 @@ namespace graphlab { execute_applys(const size_t thread_id) { context_type context(*this, graph); fixed_dense_bitset<8 * sizeof(size_t)> local_bitset; // allocate a word size = 64bits + const size_t TRY_RECV_MOD = 1000; + size_t vcount = 0; size_t napply_inc = 0; timer ti; @@ -1872,6 +1894,8 @@ namespace graphlab { active_minorstep.set_bit(lvid); // send Ax1 and Sx1 msgs send_updates(lvid, thread_id); + + if(++vcount % TRY_RECV_MOD == 0) recv_updates(); } } // end of loop over vertices to run apply completed_applys += napply_inc; @@ -1932,6 +1956,8 @@ namespace graphlab { } } // end of if out_edges/all_edges INCREMENT_EVENT(EVENT_SCATTERS, edges_touched); + // Clear the vertex program + vertex_programs[lvid] = vertex_program_type(); ++nscatter_inc; } // end of if active on this minor step } // end of loop over vertices to complete scatter operation @@ -1950,9 +1976,7 @@ namespace graphlab { local_vertex_type vertex = graph.l_vertex(lvid); foreach(const procid_t& mirror, vertex.mirrors()) { activ_exchange.send(mirror, - make_triple(vid, - vertex_programs[lvid], - edge_dirs[lvid])); + std::make_pair(vid, vertex_programs[lvid])); } } // end of send_activ @@ -1963,11 +1987,10 @@ namespace graphlab { while(activ_exchange.recv(recv_buffer)) { for (size_t i = 0;i < recv_buffer.size(); ++i) { typename activ_exchange_type::buffer_type& buffer = recv_buffer[i].buffer; - foreach(const vid_vprog_edir_triple_type& t, buffer) { - const lvid_type lvid = graph.local_vid(t.first); + foreach(const vid_vprog_pair_type& pair, buffer) { + const lvid_type lvid = graph.local_vid(pair.first); ASSERT_FALSE(graph.l_is_master(lvid)); - vertex_programs[lvid] = t.second; - edge_dirs[lvid] = t.third; + vertex_programs[lvid] = pair.second; active_minorstep.set_bit(lvid); } } @@ -2034,7 +2057,7 @@ namespace graphlab { recv_accums() { typename accum_exchange_type::recv_buffer_type recv_buffer; while(accum_exchange.recv(recv_buffer)) { - for (size_t i = 0;i < recv_buffer.size(); ++i) { + for (size_t i = 0; i < recv_buffer.size(); ++i) { typename accum_exchange_type::buffer_type& buffer = recv_buffer[i].buffer; foreach(const vid_gather_pair_type& pair, buffer) { const lvid_type lvid = graph.local_vid(pair.first); diff --git a/src/graphlab/engine/synchronous_engine.hpp b/src/graphlab/engine/synchronous_engine.hpp index a6e7410310..d2210c7636 100644 --- a/src/graphlab/engine/synchronous_engine.hpp +++ b/src/graphlab/engine/synchronous_engine.hpp @@ -1760,7 +1760,7 @@ namespace graphlab { local_vertex_type local_vertex = graph.l_vertex(lvid); const vertex_type vertex(local_vertex); const edge_dir_type scatter_dir = vprog.scatter_edges(context, vertex); - size_t edges_touched = 0; + size_t edges_touched = 0; // Loop over in edges if(scatter_dir == IN_EDGES || scatter_dir == ALL_EDGES) { foreach(local_edge_type local_edge, local_vertex.in_edges()) { @@ -1769,7 +1769,7 @@ namespace graphlab { vprog.scatter(context, vertex, edge); // elocks[local_edge.id()].unlock(); } - ++edges_touched; + ++edges_touched; } // end of if in_edges/all_edges // Loop over out edges if(scatter_dir == OUT_EDGES || scatter_dir == ALL_EDGES) { @@ -1779,9 +1779,9 @@ namespace graphlab { vprog.scatter(context, vertex, edge); // elocks[local_edge.id()].unlock(); } - ++edges_touched; + ++edges_touched; } // end of if out_edges/all_edges - INCREMENT_EVENT(EVENT_SCATTERS, edges_touched); + INCREMENT_EVENT(EVENT_SCATTERS, edges_touched); // Clear the vertex program vertex_programs[lvid] = vertex_program_type(); } // end of if active on this minor step From 812786cdf0174cdd8af7fd1cf255db5096bea772 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Sun, 17 Nov 2013 11:13:07 +0800 Subject: [PATCH 09/50] avoid locking for sending singal without message --- src/graphlab/vertex_program/context.hpp | 13 ++++++++++++- src/graphlab/vertex_program/icontext.hpp | 4 +++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/graphlab/vertex_program/context.hpp b/src/graphlab/vertex_program/context.hpp index 2cddb52cb9..24f3b6f247 100644 --- a/src/graphlab/vertex_program/context.hpp +++ b/src/graphlab/vertex_program/context.hpp @@ -119,10 +119,21 @@ namespace graphlab { * Send a message to a vertex. */ void signal(const vertex_type& vertex, - const message_type& message = message_type()) { + const message_type& message) { engine.internal_signal(vertex, message); } + /** + * Signal a vertex without a message. + * + * This new interface can avoid contention on vertex with a large number of in-edges + * for applications that scatter neighboring but without messages + * For example: PageRank with dynamic computation + */ + void signal(const vertex_type& vertex) { + engine.internal_signal(vertex); + } + /** * Send a message to an arbitrary vertex ID. * \warning If sending to neighboring vertices, the \ref signal() diff --git a/src/graphlab/vertex_program/icontext.hpp b/src/graphlab/vertex_program/icontext.hpp index 4038f2b30b..d6d315bace 100644 --- a/src/graphlab/vertex_program/icontext.hpp +++ b/src/graphlab/vertex_program/icontext.hpp @@ -211,7 +211,9 @@ namespace graphlab { * \param message [in] The message to send, defaults to message_type(). */ virtual void signal(const vertex_type& vertex, - const message_type& message = message_type()) { } + const message_type& message) { } + + virtual void signal(const vertex_type& vertex) { } /** * \brief Send a message to a vertex ID. From caede828176b3df1f81c639e16cbb86b169ecf2e Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Sun, 17 Nov 2013 11:24:09 +0800 Subject: [PATCH 10/50] [bug] using wrong edge_dirs, thanks danny bickson --- src/graphlab/engine/powerlyra_sync_engine.hpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/graphlab/engine/powerlyra_sync_engine.hpp b/src/graphlab/engine/powerlyra_sync_engine.hpp index a6aeea2ca5..5b27dcd77e 100644 --- a/src/graphlab/engine/powerlyra_sync_engine.hpp +++ b/src/graphlab/engine/powerlyra_sync_engine.hpp @@ -1659,10 +1659,8 @@ namespace graphlab { if (lvid >= graph.num_local_vertices()) break; // [TARGET]: High/Low-degree Mirrors - if(high_mirror_lvid(lvid) - || (low_mirror_lvid(lvid) // only if scatter via in-edge - && ((edge_dirs[lvid] == graphlab::IN_EDGES) - || (edge_dirs[lvid] == graphlab::ALL_EDGES)))) { + // only if scatter via in-edges will set has_message of low_mirror + if(!graph.l_is_master(lvid)) { send_message(lvid, messages[lvid], thread_id); has_message.clear_bit(lvid); // clear the message to save memory From 9c5ade0758200cabe39c697ddd84ddf521814597 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Sun, 17 Nov 2013 14:30:53 +0800 Subject: [PATCH 11/50] avoid contention for sending singal without messages --- src/graphlab/graph/builtin_parsers.hpp | 18 ++++++++++++++++++ src/graphlab/graph/distributed_graph.hpp | 3 +++ 2 files changed, 21 insertions(+) diff --git a/src/graphlab/graph/builtin_parsers.hpp b/src/graphlab/graph/builtin_parsers.hpp index e1d9e85bfb..8661d9349e 100644 --- a/src/graphlab/graph/builtin_parsers.hpp +++ b/src/graphlab/graph/builtin_parsers.hpp @@ -95,6 +95,24 @@ namespace graphlab { return true; } // end of tsv parser + /** + * \brief Parse files in the reverse tsv format + * + * This is identical to the tsv format but reverse edge direction. + * + */ + template + bool rtsv_parser(Graph& graph, const std::string& srcfilename, + const std::string& str) { + if (str.empty()) return true; + size_t source, target; + char* targetptr; + source = strtoul(str.c_str(), &targetptr, 10); + if (targetptr == NULL) return false; + target = strtoul(targetptr, NULL, 10); + if(source != target) graph.add_edge(target, source); + return true; + } // end of rtsv parser template bool csv_parser(Graph& graph, diff --git a/src/graphlab/graph/distributed_graph.hpp b/src/graphlab/graph/distributed_graph.hpp index e7936025a5..306e0e23f3 100644 --- a/src/graphlab/graph/distributed_graph.hpp +++ b/src/graphlab/graph/distributed_graph.hpp @@ -2513,6 +2513,9 @@ namespace graphlab { } else if (format == "tsv") { line_parser = builtin_parsers::tsv_parser; load(path, line_parser); + } else if (format == "rtsv") { + line_parser = builtin_parsers::rtsv_parser; + load(path, line_parser); } else if (format == "csv") { line_parser = builtin_parsers::csv_parser; load(path, line_parser); From e0ac6661881540558e0c15b849ae02c29e427758 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Sun, 17 Nov 2013 15:08:56 +0800 Subject: [PATCH 12/50] opt hybrid and ginger for standalone case --- .../distributed_hybrid_ginger_ingress.hpp | 604 +++++++++++------- .../ingress/distributed_hybrid_ingress.hpp | 43 +- .../ingress/distributed_ingress_base.hpp | 52 +- 3 files changed, 416 insertions(+), 283 deletions(-) diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp index 57e4f6e6c2..a70a606b4c 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp @@ -41,6 +41,7 @@ #include +#undef TUNING namespace graphlab { template class distributed_graph; @@ -67,21 +68,14 @@ namespace graphlab { typedef typename graph_type::mirror_type mirror_type; - typedef typename hopscotch_map::value_type - vid2lvid_pair_type; - typedef typename buffered_exchange::buffer_type vertex_id_buffer_type; - typedef typename graph_type::zone_type zone_type; - /// one-by-one ingress. e.g., SNAP typedef typename base_type::edge_buffer_record edge_buffer_record; typedef typename buffered_exchange::buffer_type edge_buffer_type; - - typedef typename base_type::vertex_buffer_record vertex_buffer_record; typedef typename buffered_exchange::buffer_type vertex_buffer_type; @@ -108,20 +102,25 @@ namespace graphlab { typedef typename base_type::vertex_negotiator_record vertex_negotiator_record; - typedef typename std::pair mht_entry_type; - - /** Type of the master location hash table: - * a map from vertex id to the location of its master. */ - typedef typename boost::unordered_map master_hash_table_type; - typedef typename std::pair master_pair_type; - typedef typename buffered_exchange::buffer_type master_buffer_type; - - typedef typename std::pair proc_edges_pair_type; + /// ginger structure + /** Type of the master location hash table: [vertex-id, location-of-master] */ + typedef typename boost::unordered_map + master_hash_table_type; + typedef typename std::pair + master_pair_type; + typedef typename buffered_exchange::buffer_type + master_buffer_type; + + typedef typename std::pair + proc_edges_pair_type; typedef typename buffered_exchange::buffer_type proc_edges_buffer_type; - /** Number of edges and vertices in graph. */ - buffered_exchange hybrid_edge_exchange; + /* ingress edges */ + buffered_exchange single_edge_exchange; + buffered_exchange high_edge_exchange; + buffered_exchange low_edge_exchange; + /* ingress vertex data */ buffered_exchange hybrid_vertex_exchange; buffered_exchange temporary_vertex_exchange; @@ -139,18 +138,19 @@ namespace graphlab { /// The underlying distributed graph object that is being loaded graph_type& graph; - buffered_exchange high_edge_exchange; - buffered_exchange low_edge_exchange; + /// threshold to divide high-degree and low-degree vertices + size_t threshold; + + bool standalone; + + std::vector hybrid_edges; //these are used for edges balance - buffered_exchange< proc_edges_pair_type > proc_edges_exchange; std::vector proc_edges_incremental; - std::vector proc_num_vertices; - /// threshold to divide high-degree and low-degree vertices - size_t threshold; + /// heuristic model from fennel /// records about the number of edges and vertices in the graph /// given from the commandline size_t nedges; @@ -169,15 +169,20 @@ namespace graphlab { size_t threshold = 100, size_t nedges = 0, size_t nverts = 0, size_t interval = std::numeric_limits::max()) : base_type(dc, graph), - hybrid_edge_exchange(dc), hybrid_vertex_exchange(dc),temporary_vertex_exchange(dc), - master_exchange(dc), - hybrid_rpc(dc, this),graph(graph),high_edge_exchange(dc),low_edge_exchange(dc),proc_edges_exchange(dc), - proc_edges_incremental(dc.numprocs()),proc_num_vertices(dc.numprocs()), threshold(threshold), + single_edge_exchange(dc), high_edge_exchange(dc), low_edge_exchange(dc), + hybrid_vertex_exchange(dc), temporary_vertex_exchange(dc), master_exchange(dc), + hybrid_rpc(dc, this), graph(graph), threshold(threshold), + proc_edges_exchange(dc), proc_edges_incremental(dc.numprocs()), + proc_num_vertices(dc.numprocs()), nedges(nedges), nverts(nverts), interval(interval) { + ASSERT_GT(nedges, 0); ASSERT_GT(nverts, 0); + gamma = 1.5; alpha = sqrt(dc.numprocs()) * nedges / pow(nverts, gamma); - if(nedges<=0) - nedges=1; + + /* fast pass for standalone case. */ + standalone = hybrid_rpc.numprocs() == 1; + hybrid_rpc.barrier(); } // end of constructor ~distributed_hybrid_ginger_ingress() { } @@ -188,10 +193,15 @@ namespace graphlab { */ void add_edge(vertex_id_type source, vertex_id_type target, const EdgeData& edata) { - const procid_t owning_proc = - graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); const edge_buffer_record record(source, target, edata); - hybrid_edge_exchange.send(owning_proc, record); + if (standalone) { + /* Fast pass for standalone case. */ + hybrid_edges.push_back(record); + } else { + const procid_t owning_proc = + graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); + single_edge_exchange.send(owning_proc, record); + } } // end of add edge /** Add edges to the ingress object using different assignment policies. @@ -199,123 +209,183 @@ namespace graphlab { * For high degree edges, hashing from its source vertex; * for low degree edges, using a heuristic way named ginger. */ - void add_edges(std::vector& in, vertex_id_type vid, - const std::vector& edatas) { - procid_t owning_proc; - if (in.size()>=threshold) { - owning_proc = graph_hash::hash_vertex(vid) % base_type::rpc.numprocs(); + void add_edges(std::vector& in, vertex_id_type out, + const std::vector& edatas) { + if (standalone) { + /* fast pass for standalone case. */ + for(size_t i = 0; i < in.size();i++){ + const edge_buffer_record record(in[i], out, edatas[i]); + hybrid_edges.push_back(record); + } } else { - owning_proc = - base_type::edge_decision.edge_to_proc_ginger(vid, mht, - mht_incremental, proc_num_vertices, alpha, gamma, in); - } - - typedef typename base_type::edge_buffer_record edge_buffer_record; - if(in.size()>=threshold){ - for (size_t i = 0; i < in.size(); ++i) { - edge_buffer_record record(in[i], vid, edatas[i]); - high_edge_exchange.send(owning_proc, record); - } - } - else{ - for (size_t i = 0; i < in.size(); ++i) { - edge_buffer_record record(in[i], vid, edatas[i]); - low_edge_exchange.send(owning_proc, record); + procid_t owning_proc = 0; + if (in.size() >= threshold) { + // TODO: no need send, just resend latter + owning_proc = graph_hash::hash_vertex(out) % base_type::rpc.numprocs(); + for (size_t i = 0; i < in.size(); ++i) { + edge_buffer_record record(in[i], out, edatas[i]); + high_edge_exchange.send(owning_proc, record); + } + } else { + owning_proc = base_type::edge_decision.edge_to_proc_ginger( + out, mht, mht_incremental, proc_num_vertices, + alpha, gamma, in); + for (size_t i = 0; i < in.size(); ++i) { + edge_buffer_record record(in[i], out, edatas[i]); + low_edge_exchange.send(owning_proc, record); + } + + // update local counter + proc_edges_incremental[owning_proc] += in.size(); } - proc_edges_incremental[owning_proc]+=in.size(); //edge balance - } - size_t numprocs = base_type::rpc.numprocs(); - mht_incremental[vid] = owning_proc; - if (mht_incremental.size() > interval) { - //update mht - for (typename master_hash_table_type::iterator it = mht_incremental.begin(); - it != mht_incremental.end(); ++it) { - for (procid_t i = 0; i < numprocs; ++i) { - if (i != hybrid_rpc.procid()) - master_exchange.send(i, master_pair_type(it->first, it->second)); + // update mapping table for ginger + size_t numprocs = base_type::rpc.numprocs(); + mht_incremental[out] = owning_proc; + + // broadcast local counters + if (mht_incremental.size() > interval) { + //update mht + for (typename master_hash_table_type::iterator it = mht_incremental.begin(); + it != mht_incremental.end(); ++it) { + for (procid_t i = 0; i < numprocs; ++i) { + if (i != hybrid_rpc.procid()) + master_exchange.send(i, master_pair_type(it->first, it->second)); + } + mht[it->first] = it->second; } - mht[it->first] = it->second; - } - master_exchange.partial_flush(0); - mht_incremental.clear(); - master_buffer_type master_buffer; - procid_t proc; - while(master_exchange.recv(proc, master_buffer)) { - foreach(const master_pair_type& pair, master_buffer) { - mht[pair.first] = pair.second; - //proc_num_vertices[pair.second]++; + master_exchange.partial_flush(0); + mht_incremental.clear(); + master_buffer_type master_buffer; + procid_t proc; + while(master_exchange.recv(proc, master_buffer)) { + foreach(const master_pair_type& pair, master_buffer) { + mht[pair.first] = pair.second; + //proc_num_vertices[pair.second]++; + } } - } - - //update proc_edges for edges balance - for(procid_t p=0;p batch_record_map_type; - batch_record_map_type batch_map; + void assign_edges() { + single_edge_exchange.flush(); + if (single_edge_exchange.size() > 0) { + typedef typename boost::unordered_map + batch_record_map_type; + batch_record_map_type batch_map; - hybrid_edge_exchange.flush(); - - edge_buffer_type edge_buffer; - procid_t proc = -1; - while(hybrid_edge_exchange.recv(proc, edge_buffer)) { - foreach(const edge_buffer_record& rec, edge_buffer) { - batch_map[rec.target].sources.push_back(rec.source); - batch_map[rec.target].edatas.push_back(rec.edata); + edge_buffer_type edge_buffer; + procid_t proc = -1; + while (single_edge_exchange.recv(proc, edge_buffer)) { + foreach(const edge_buffer_record& rec, edge_buffer) { + batch_map[rec.target].sources.push_back(rec.source); + batch_map[rec.target].edatas.push_back(rec.edata); + } + } + // re-assigne + for (typename batch_record_map_type::iterator it = batch_map.begin(); + it != batch_map.end(); ++it) { + add_edges(it->second.sources, it->first,it->second.edatas); } - } - hybrid_edge_exchange.clear(); - - hybrid_rpc.full_barrier(); - for (typename batch_record_map_type::iterator it = batch_map.begin();it != batch_map.end(); ++it) { - add_edges(it->second.sources, it->first,it->second.edatas); + single_edge_exchange.clear(); + //hybrid_rpc.full_barrier(); } } void finalize() { graphlab::timer ti; - snap_ingress(); - size_t nprocs = hybrid_rpc.numprocs(); procid_t l_procid = hybrid_rpc.procid(); - std::vector hybrid_edges; size_t nedges; + if (l_procid == 0) { + memory_info::log_usage("start finalizing"); + logstream(LOG_EMPH) << "ginger finalizing ..." + << " #vertices=" << graph.local_graph.num_vertices() + << " #edges=" << graph.local_graph.num_edges() + << " threshold=" << threshold + << std::endl; + } + + /**************************************************************************/ + /* */ + /* Assign edges for one-by-one format */ + /* */ + /**************************************************************************/ + if (!standalone) assign_edges(); + + /** + * Fast pass for redundant finalization with no graph changes. + */ + size_t changed_size; + + // flush and count changes + if (standalone) { + nedges = hybrid_edges.size(); + hybrid_vertex_exchange.flush(); + + changed_size = nedges + hybrid_vertex_exchange.size(); + } else { + high_edge_exchange.flush(); + low_edge_exchange.flush(); + temporary_vertex_exchange.flush(); - { // send mht to all other nodes + changed_size = high_edge_exchange.size() + + low_edge_exchange.size() + + hybrid_vertex_exchange.size(); + } + + hybrid_rpc.all_reduce(changed_size); + if (changed_size == 0) { + logstream(LOG_INFO) << "Skipping Graph Finalization because no changes happened..." << std::endl; + return; + } + + /**************************************************************************/ + /* */ + /* prepare hybrid ingress */ + /* */ + /**************************************************************************/ + if (!standalone) { + /* exchange mapping table*/ for (typename master_hash_table_type::iterator it = mht_incremental.begin(); it != mht_incremental.end(); ++it) { for (procid_t i = 0; i < nprocs; ++i) { @@ -324,147 +394,191 @@ namespace graphlab { } mht[it->first] = it->second; } - mht_incremental.clear(); + master_exchange.flush(); - master_buffer_type master_buffer; - procid_t proc; + procid_t proc = -1; while(master_exchange.recv(proc, master_buffer)) { foreach(const master_pair_type& pair, master_buffer) { mht[pair.first] = pair.second; } } master_exchange.clear(); - - - std::vector hybrid_edges; - hopscotch_map in_degree_set; - - - if (l_procid == 0) { - memory_info::log_usage("start finalizing"); - logstream(LOG_EMPH) << "hybrid ginger finalizing Graph ..." - << " #vertices=" << graph.local_graph.num_vertices() - << " #edges=" << graph.local_graph.num_edges() - << " threshold=" << threshold +#ifdef TUNING + if(l_procid == 0) { + memory_info::log_usage("exchange mapping done."); + logstream(LOG_EMPH) << "exchange mapping: " + << ti.current_time() + << " secs" << std::endl; } - } - - +#endif - - /**************************************************************************/ - /* */ - /* batch ingress */ - /* */ - /**************************************************************************/ - { - high_edge_exchange.flush(); - low_edge_exchange.flush(); - - nedges=low_edge_exchange.size(); - hybrid_edges.reserve(nedges+high_edge_exchange.size()); + /* collect edges for batch ingress (e.g. RADJ) */ + nedges = low_edge_exchange.size(); + hybrid_edges.reserve(nedges + high_edge_exchange.size()); edge_buffer_type edge_buffer; - procid_t proc = -1; + proc = -1; while(low_edge_exchange.recv(proc, edge_buffer)) { foreach(const edge_buffer_record& rec, edge_buffer) { if (mht.find(rec.source) == mht.end()) - mht[rec.source] = graph_hash::hash_vertex(rec.source) % hybrid_rpc.numprocs(); + mht[rec.source] = graph_hash::hash_vertex(rec.source) % nprocs; hybrid_edges.push_back(rec); } } low_edge_exchange.clear(); - - hybrid_rpc.full_barrier(); - + hybrid_rpc.full_barrier(); // sync before reusing + + // re-send edges of high-degree vertices + proc = -1; while(high_edge_exchange.recv(proc, edge_buffer)) { foreach(const edge_buffer_record& rec, edge_buffer) { if (mht.find(rec.source) == mht.end()) - mht[rec.source] = graph_hash::hash_vertex(rec.source) % hybrid_rpc.numprocs(); + mht[rec.source] = graph_hash::hash_vertex(rec.source) % nprocs; + const procid_t source_owner_proc = mht[rec.source]; - - if(source_owner_proc == l_procid){ + if (source_owner_proc == l_procid) { hybrid_edges.push_back(rec); - nedges++; + ++nedges; } else { low_edge_exchange.send(source_owner_proc, rec); } } } high_edge_exchange.clear(); - - low_edge_exchange.flush(); - - if(l_procid == 0) - logstream(LOG_INFO) << "receive high-degree mirrors: " - << low_edge_exchange.size() << std::endl; - { - edge_buffer_type edge_buffer; - procid_t proc = -1; - while(low_edge_exchange.recv(proc, edge_buffer)) { - foreach(const edge_buffer_record& rec, edge_buffer) { - mht[rec.source] = l_procid; - hybrid_edges.push_back(rec); - nedges++; - } + + // receive edges of high-degree vertices + low_edge_exchange.flush(); + proc = -1; + while(low_edge_exchange.recv(proc, edge_buffer)) { + foreach(const edge_buffer_record& rec, edge_buffer) { + mht[rec.source] = l_procid; + hybrid_edges.push_back(rec); + ++nedges; } } low_edge_exchange.clear(); } + if(l_procid == 0) { + memory_info::log_usage("prepare ginger finalizing done."); + logstream(LOG_EMPH) << "prepare ginger finalizing. (" + << ti.current_time() + << " secs)" + << std::endl; + } + // connect to base finalize() - // pass nedges and hybrid_edges to the base finalize - modified_base_finalize(nedges, hybrid_edges); + modified_base_finalize(nedges); + if(l_procid == 0) { + memory_info::log_usage("base finalizing done."); + logstream(LOG_EMPH) << "base finalizing. (" + << ti.current_time() + << " secs)" + << std::endl; + } set_vertex_type(); + if(l_procid == 0) { + memory_info::log_usage("set vertex type done."); + logstream(LOG_EMPH) << "set vertex type. (" + << ti.current_time() + << " secs)" + << std::endl; + } if(l_procid == 0) - logstream(LOG_EMPH) << "orignal hybrid finalizing graph done. (" + logstream(LOG_EMPH) << "ginger finalizing graph done. (" << ti.current_time() << " secs)" << std::endl; - } // end of finalize + } // end of finalize void set_vertex_type() { graphlab::timer ti; procid_t l_procid = hybrid_rpc.procid(); + size_t high_master = 0, high_mirror = 0, low_master = 0, low_mirror = 0; for (size_t lvid = 0; lvid < graph.num_local_vertices(); lvid++) { vertex_record& vrec = graph.lvid2record[lvid]; - if (vrec.owner == l_procid) { - if (vrec.num_in_edges >= threshold) - vrec.type = graph_type::HIGH_MASTER; - else vrec.type = graph_type::LOW_MASTER; - } else { - if (vrec.num_in_edges >= threshold) + if (vrec.num_in_edges >= threshold) { + if (vrec.owner == l_procid) { + vrec.type = graph_type::HIGH_MASTER; + high_master ++; + } else { vrec.type = graph_type::HIGH_MIRROR; - else vrec.type = graph_type::LOW_MIRROR; - } + high_mirror ++; + } + } else { + if (vrec.owner == l_procid) { + vrec.type = graph_type::LOW_MASTER; + low_master ++; + } else { + vrec.type = graph_type::LOW_MIRROR; + low_mirror ++; + } + } } +//#ifdef TUNING + // Compute the total number of high-degree and low-degree vertices + std::vector swap_counts(hybrid_rpc.numprocs()); + + swap_counts[l_procid] = high_master; + hybrid_rpc.all_gather(swap_counts); + high_master = 0; + foreach(size_t count, swap_counts) high_master += count; + + swap_counts[l_procid] = high_mirror; + hybrid_rpc.all_gather(swap_counts); + high_mirror = 0; + foreach(size_t count, swap_counts) high_mirror += count; + + swap_counts[l_procid] = low_master; + hybrid_rpc.all_gather(swap_counts); + low_master = 0; + foreach(size_t count, swap_counts) low_master += count; + + swap_counts[l_procid] = low_mirror; + hybrid_rpc.all_gather(swap_counts); + low_mirror = 0; + foreach(size_t count, swap_counts) low_mirror += count; + if(l_procid == 0) { + logstream(LOG_EMPH) << "hybrid info: master [" + << high_master << " " + << low_master << " " + << ((high_master*1.0)/(high_master+low_master)) << "]" + << std::endl; + if ((high_mirror + low_mirror) > 0) + logstream(LOG_EMPH) << "hybrid info: mirror [" + << high_mirror << " " + << low_mirror << " " + << ((high_mirror*1.0)/(high_mirror+low_mirror)) << "]" + << std::endl; + memory_info::log_usage("set vertex type done."); logstream(LOG_EMPH) << "set vertex type: " << ti.current_time() << " secs" << std::endl; } +//#endif } + /* do the same job as original base finalize except for * extracting edges from hybrid_edges instead of original edge_buffer; * and using mht to tracing the master location of each vertex. */ - void modified_base_finalize(size_t nedges, - std::vector& hybrid_edges) { - + void modified_base_finalize(size_t nedges) { graphlab::timer ti; procid_t l_procid = hybrid_rpc.procid(); + size_t nprocs = hybrid_rpc.numprocs(); hybrid_rpc.full_barrier(); @@ -480,6 +594,10 @@ namespace graphlab { first_time_finalize = false; } + + typedef typename hopscotch_map::value_type + vid2lvid_pair_type; + /** * \internal * Buffer storage for new vertices to the local graph. @@ -500,14 +618,6 @@ namespace graphlab { dense_bitset updated_lvids(graph.vid2lvid.size()); - /**************************************************************************/ - /* */ - /* flush any additional data */ - /* */ - /**************************************************************************/ - temporary_vertex_exchange.flush(); - - /**************************************************************************/ /* */ /* Construct local graph */ @@ -518,9 +628,6 @@ namespace graphlab { graph.local_graph.reserve_edge_space(nedges + 1); foreach(const edge_buffer_record& rec, hybrid_edges) { - // skip re-sent edges - if (rec.source == vertex_id_type(-1)) continue; - // Get the source_vlid; lvid_type source_lvid(-1); if(graph.vid2lvid.find(rec.source) == graph.vid2lvid.end()) { @@ -553,53 +660,61 @@ namespace graphlab { ASSERT_EQ(graph.vid2lvid.size() + vid2lvid_buffer.size(), graph.local_graph.num_vertices()); +#ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("populating local graph done."); - logstream(LOG_EMPH) << "populating local graph: " + memory_info::log_usage("base::populating local graph done."); + logstream(LOG_EMPH) << "base::populating local graph: " << ti.current_time() << " secs" << std::endl; } - +#endif // Finalize local graph graph.local_graph.finalize(); - logstream(LOG_INFO) << "local graph info: " << std::endl +#ifdef TUNING + logstream(LOG_INFO) << "base::local graph info: " << std::endl << "\t nverts: " << graph.local_graph.num_vertices() << std::endl << "\t nedges: " << graph.local_graph.num_edges() << std::endl; if(l_procid == 0) { - memory_info::log_usage("finalizing local graph done."); - logstream(LOG_EMPH) << "finalizing local graph: " + memory_info::log_usage("base::finalizing local graph done."); + logstream(LOG_EMPH) << "base::finalizing local graph: " << ti.current_time() << " secs" << std::endl; } +#endif } /**************************************************************************/ /* */ - /* receive and add vertex data to masters */ + /* Receive and add vertex data to masters */ /* */ /**************************************************************************/ // Setup the map containing all the vertices being negotiated by this machine { - if (temporary_vertex_exchange.size() > 0) { - vertex_buffer_type vertex_buffer; procid_t sending_proc(-1); - while(temporary_vertex_exchange.recv(sending_proc, vertex_buffer)) { - foreach(const vertex_buffer_record& rec, vertex_buffer) { - if (mht.find(rec.vid) == mht.end()) - mht[rec.vid] = graph_hash::hash_vertex(rec.vid) % hybrid_rpc.numprocs(); - hybrid_vertex_exchange.send(mht[rec.vid], rec); + if (!standalone) { + // has flushed in finalize() + if (temporary_vertex_exchange.size() > 0) { + vertex_buffer_type vertex_buffer; procid_t sending_proc(-1); + while (temporary_vertex_exchange.recv(sending_proc, vertex_buffer)) { + foreach (const vertex_buffer_record& rec, vertex_buffer) { + if (mht.find(rec.vid) == mht.end()) + mht[rec.vid] = graph_hash::hash_vertex(rec.vid) % nprocs; + hybrid_vertex_exchange.send(mht[rec.vid], rec); + } } + temporary_vertex_exchange.clear(); } - temporary_vertex_exchange.clear(); + + // match standalone code + hybrid_vertex_exchange.flush(); } - hybrid_vertex_exchange.flush(); - // receive any vertex data sent by other machines + // receive any vertex data sent by other machines if (hybrid_vertex_exchange.size() > 0) { vertex_buffer_type vertex_buffer; procid_t sending_proc(-1); while(hybrid_vertex_exchange.recv(sending_proc, vertex_buffer)) { @@ -617,7 +732,7 @@ namespace graphlab { updated_lvids.set_bit(lvid); } if (distributed_hybrid_ginger_ingress::vertex_combine_strategy - && lvid < graph.num_local_vertices()) { + && lvid < graph.num_local_vertices()) { distributed_hybrid_ginger_ingress::vertex_combine_strategy( graph.l_vertex(lvid).data(), rec.vdata); } else { @@ -626,30 +741,26 @@ namespace graphlab { } } hybrid_vertex_exchange.clear(); - - logstream(LOG_INFO) << " #vert-msgs=" << hybrid_vertex_exchange.size() +#ifdef TUNING + logstream(LOG_INFO) << "base::#vert-msgs=" << hybrid_vertex_exchange.size() << std::endl; if(l_procid == 0) { - memory_info::log_usage("adding vertex data done."); - logstream(LOG_EMPH) << "adding vertex data: " + memory_info::log_usage("base::adding vertex data done."); + logstream(LOG_EMPH) << "base::adding vertex data: " << ti.current_time() << " secs" << std::endl; } +#endif } } // end of loop to populate vrecmap /**************************************************************************/ /* */ - /* assign vertex data and allocate vertex (meta)data space */ + /* Assign vertex data and allocate vertex (meta)data space */ /* */ /**************************************************************************/ - -#ifdef INGRESS_DEBUG - std::vector degree; - degree.resize(graph_type::NUM_ZONE_TYPES, 0); -#endif { // determine masters for all negotiated vertices const size_t local_nverts = graph.vid2lvid.size() + vid2lvid_buffer.size(); @@ -659,13 +770,18 @@ namespace graphlab { foreach(const vid2lvid_pair_type& pair, vid2lvid_buffer) { vertex_record& vrec = graph.lvid2record[pair.second]; vrec.gvid = pair.first; - if (mht.find(pair.first) == mht.end()) - mht[pair.first] = graph_hash::hash_vertex(pair.first) % hybrid_rpc.numprocs(); - const procid_t source_owner_proc = mht[pair.first]; - vrec.owner = source_owner_proc; + if (standalone) { + vrec.owner = 0; + } else { + if (mht.find(pair.first) == mht.end()) + mht[pair.first] = graph_hash::hash_vertex(pair.first) % nprocs; + const procid_t source_owner_proc = mht[pair.first]; + vrec.owner = source_owner_proc; + } } ASSERT_EQ(local_nverts, graph.local_graph.num_vertices()); ASSERT_EQ(graph.lvid2record.size(), graph.local_graph.num_vertices()); +#ifdef TUNING if(l_procid == 0) { memory_info::log_usage("allocating lvid2record done."); logstream(LOG_EMPH) << "allocating lvid2record: " @@ -673,16 +789,16 @@ namespace graphlab { << " secs" << std::endl; } - +#endif mht.clear(); } /**************************************************************************/ /* */ - /* master handshake */ + /* Master handshake */ /* */ /**************************************************************************/ - { + if (!standalone) { #ifdef _OPENMP buffered_exchange vid_buffer(hybrid_rpc.dc(), omp_get_max_threads()); #else @@ -737,7 +853,7 @@ namespace graphlab { vid_buffer.clear(); if (!flying_vids.empty()) { - logstream(LOG_INFO) << "#flying-own-nverts=" + logstream(LOG_INFO) << "base::#flying-own-nverts=" << flying_vids.size() << std::endl; @@ -759,18 +875,20 @@ namespace graphlab { } } // end of master handshake +#ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("master handshake done."); - logstream(LOG_EMPH) << "master handshake: " + memory_info::log_usage("base::master handshake done."); + logstream(LOG_EMPH) << "base::master handshake: " << ti.current_time() << " secs" << std::endl; } +#endif /**************************************************************************/ /* */ - /* merge in vid2lvid_buffer */ + /* Merge in vid2lvid_buffer */ /* */ /**************************************************************************/ { @@ -788,12 +906,13 @@ namespace graphlab { /**************************************************************************/ /* */ - /* synchronize vertex data and meta information */ + /* Synchronize vertex data and meta information */ /* */ /**************************************************************************/ + // TODO: optimization for standalone { // construct the vertex set of changed vertices - + // Fast pass for first time finalize; vertex_set changed_vset(true); @@ -827,7 +946,9 @@ namespace graphlab { } } - base_type::exchange_global_info(); + base_type::exchange_global_info(standalone); + +#ifdef TUNING if(l_procid == 0) { memory_info::log_usage("exchange global info done."); logstream(LOG_EMPH) << "exchange global info: " @@ -835,6 +956,7 @@ namespace graphlab { << " secs" << std::endl; } +#endif } // end of modified base finalize private: diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp index ac5694309b..8f6100f2d3 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp @@ -491,6 +491,7 @@ namespace graphlab { void modified_base_finalize(size_t nedges) { graphlab::timer ti; procid_t l_procid = hybrid_rpc.procid(); + size_t nprocs = hybrid_rpc.numprocs(); hybrid_rpc.full_barrier(); @@ -597,8 +598,8 @@ namespace graphlab { graph.local_graph.num_vertices()); #ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("base::populating local graph done."); - logstream(LOG_EMPH) << "base::populating local graph: " + memory_info::log_usage("populating local graph done."); + logstream(LOG_EMPH) << "populating local graph: " << ti.current_time() << " secs" << std::endl; @@ -607,15 +608,15 @@ namespace graphlab { // Finalize local graph graph.local_graph.finalize(); #ifdef TUNING - logstream(LOG_INFO) << "base::local graph info: " << std::endl + logstream(LOG_INFO) << "local graph info: " << std::endl << "\t nverts: " << graph.local_graph.num_vertices() << std::endl << "\t nedges: " << graph.local_graph.num_edges() << std::endl; if(l_procid == 0) { - memory_info::log_usage("base::finalizing local graph done."); - logstream(LOG_EMPH) << "base::finalizing local graph: " + memory_info::log_usage("finalizing local graph done."); + logstream(LOG_EMPH) << "finalizing local graph: " << ti.current_time() << " secs" << std::endl; @@ -662,8 +663,8 @@ namespace graphlab { logstream(LOG_INFO) << "base::#vert-msgs=" << hybrid_vertex_exchange.size() << std::endl; if(l_procid == 0) { - memory_info::log_usage("base::adding vertex data done."); - logstream(LOG_EMPH) << "base::adding vertex data: " + memory_info::log_usage("adding vertex data done."); + logstream(LOG_EMPH) << "adding vertex data: " << ti.current_time() << " secs" << std::endl; @@ -687,14 +688,17 @@ namespace graphlab { foreach(const vid2lvid_pair_type& pair, vid2lvid_buffer) { vertex_record& vrec = graph.lvid2record[pair.second]; vrec.gvid = pair.first; - vrec.owner = graph_hash::hash_vertex(pair.first) % hybrid_rpc.numprocs(); + if (standalone) + vrec.owner = 0; + else + vrec.owner = graph_hash::hash_vertex(pair.first) % nprocs; } ASSERT_EQ(local_nverts, graph.local_graph.num_vertices()); ASSERT_EQ(graph.lvid2record.size(), graph.local_graph.num_vertices()); #ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("base::allocating lvid2record done."); - logstream(LOG_EMPH) << "base::allocating lvid2record: " + memory_info::log_usage("allocating lvid2record done."); + logstream(LOG_EMPH) << "allocating lvid2record: " << ti.current_time() << " secs" << std::endl; @@ -708,7 +712,7 @@ namespace graphlab { /* Master handshake */ /* */ /**************************************************************************/ - { + if (!standalone) { #ifdef _OPENMP buffered_exchange vid_buffer(hybrid_rpc.dc(), omp_get_max_threads()); #else @@ -763,7 +767,7 @@ namespace graphlab { vid_buffer.clear(); if (!flying_vids.empty()) { - logstream(LOG_INFO) << "base::#flying-own-nverts=" + logstream(LOG_INFO) << "#flying-own-nverts=" << flying_vids.size() << std::endl; @@ -787,8 +791,8 @@ namespace graphlab { #ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("base::master handshake done."); - logstream(LOG_EMPH) << "base::master handshake: " + memory_info::log_usage("master handshake done."); + logstream(LOG_EMPH) << "master handshake: " << ti.current_time() << " secs" << std::endl; @@ -819,6 +823,7 @@ namespace graphlab { /* Synchronize vertex data and meta information */ /* */ /**************************************************************************/ + // TODO: optimization for standalone { // construct the vertex set of changed vertices @@ -848,20 +853,20 @@ namespace graphlab { vrecord_sync_gas.exec(changed_vset); if(l_procid == 0) { - memory_info::log_usage("base::synchronizing vertex (meta)data done."); - logstream(LOG_EMPH) << "base::synchrionizing vertex (meta)data: " + memory_info::log_usage("synchronizing vertex (meta)data done."); + logstream(LOG_EMPH) << "synchrionizing vertex (meta)data: " << ti.current_time() << " secs" << std::endl; } } - base_type::exchange_global_info(); + base_type::exchange_global_info(standalone); #ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("base::exchange global info done."); - logstream(LOG_EMPH) << "base::exchange global info: " + memory_info::log_usage("exchange global info done."); + logstream(LOG_EMPH) << "exchange global info: " << ti.current_time() << " secs" << std::endl; diff --git a/src/graphlab/graph/ingress/distributed_ingress_base.hpp b/src/graphlab/graph/ingress/distributed_ingress_base.hpp index f2cdc574d2..a23794e00f 100644 --- a/src/graphlab/graph/ingress/distributed_ingress_base.hpp +++ b/src/graphlab/graph/ingress/distributed_ingress_base.hpp @@ -496,13 +496,13 @@ namespace graphlab { memory_info::log_usage("Finished synchronizing vertex (meta)data"); } - exchange_global_info(); + exchange_global_info(false); } // end of finalize /* Exchange graph statistics among all nodes and compute * global statistics for the distributed graph. */ - void exchange_global_info () { + void exchange_global_info (bool standalone) { // Count the number of vertices owned locally graph.local_own_nverts = 0; foreach(const vertex_record& record, graph.lvid2record) @@ -512,33 +512,39 @@ namespace graphlab { logstream(LOG_INFO) << "Graph Finalize: exchange global statistics " << std::endl; - // Compute edge counts - std::vector swap_counts(rpc.numprocs()); - swap_counts[rpc.procid()] = graph.num_local_edges(); - rpc.all_gather(swap_counts); - graph.nedges = 0; - foreach(size_t count, swap_counts) graph.nedges += count; - - - // compute vertex count - swap_counts[rpc.procid()] = graph.num_local_own_vertices(); - rpc.all_gather(swap_counts); - graph.nverts = 0; - foreach(size_t count, swap_counts) graph.nverts += count; - - // compute replicas - swap_counts[rpc.procid()] = graph.num_local_vertices(); - rpc.all_gather(swap_counts); - graph.nreplicas = 0; - foreach(size_t count, swap_counts) graph.nreplicas += count; - + if (standalone) { + graph.nedges = graph.num_local_edges(); + graph.nverts = graph.num_local_own_vertices(); + graph.nreplicas = graph.num_local_vertices(); + } else { + // Compute edge counts + std::vector swap_counts(rpc.numprocs()); + swap_counts[rpc.procid()] = graph.num_local_edges(); + rpc.all_gather(swap_counts); + graph.nedges = 0; + foreach(size_t count, swap_counts) graph.nedges += count; + + + // compute vertex count + swap_counts[rpc.procid()] = graph.num_local_own_vertices(); + rpc.all_gather(swap_counts); + graph.nverts = 0; + foreach(size_t count, swap_counts) graph.nverts += count; + + // compute replicas + swap_counts[rpc.procid()] = graph.num_local_vertices(); + rpc.all_gather(swap_counts); + graph.nreplicas = 0; + foreach(size_t count, swap_counts) graph.nreplicas += count; + } if (rpc.procid() == 0) { logstream(LOG_EMPH) << "Graph info: " << "\n\t nverts: " << graph.num_vertices() << "\n\t nedges: " << graph.num_edges() << "\n\t nreplicas: " << graph.nreplicas - << "\n\t replication factor: " << (double)graph.nreplicas/graph.num_vertices() + << "\n\t replication factor: " + << (double)graph.nreplicas/graph.num_vertices() << std::endl; } } From 7ba24cce751235b9890d677df0519f37112dd12f Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Sun, 17 Nov 2013 23:46:15 +0800 Subject: [PATCH 13/50] provide consistent output for evaluation --- toolkits/collaborative_filtering/als.cpp | 6 +++--- toolkits/graph_analytics/pagerank.cpp | 17 ++++++++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/toolkits/collaborative_filtering/als.cpp b/toolkits/collaborative_filtering/als.cpp index ded5470ef5..613fc8317f 100644 --- a/toolkits/collaborative_filtering/als.cpp +++ b/toolkits/collaborative_filtering/als.cpp @@ -624,7 +624,7 @@ int main(int argc, char** argv) { dc.cout() << "Finalizing graph." << std::endl; timer.start(); - graph.finalize(); + graph.finalize(); dc.cout() << "Finalizing graph. Finished in " << timer.current_time() << std::endl; @@ -695,9 +695,9 @@ int main(int argc, char** argv) { save_edges, threads_per_machine); //save the linear model graph.save(predictions + ".U", linear_model_saver_U(), - gzip_output, save_edges, save_vertices, threads_per_machine); + gzip_output, save_edges, save_vertices, threads_per_machine); graph.save(predictions + ".V", linear_model_saver_V(), - gzip_output, save_edges, save_vertices, threads_per_machine); + gzip_output, save_edges, save_vertices, threads_per_machine); } diff --git a/toolkits/graph_analytics/pagerank.cpp b/toolkits/graph_analytics/pagerank.cpp index 377d97a6c8..cdc55343be 100644 --- a/toolkits/graph_analytics/pagerank.cpp +++ b/toolkits/graph_analytics/pagerank.cpp @@ -243,6 +243,7 @@ int main(int argc, char** argv) { << timer.current_time() << std::endl; // must call finalize before querying the graph dc.cout() << "Finalizing graph." << std::endl; + timer.start(); graph.finalize(); dc.cout() << "Finalizing graph. Finished in " << timer.current_time() << std::endl; @@ -256,11 +257,16 @@ int main(int argc, char** argv) { // Running The Engine ------------------------------------------------------- graphlab::omni_engine engine(dc, graph, exec_type, clopts); engine.signal_all(); + timer.start(); engine.start(); - const double runtime = engine.elapsed_seconds(); - dc.cout() << "Finished Running engine in " << runtime - << " seconds." << std::endl; - + const double runtime = timer.current_time(); + dc.cout() << "----------------------------------------------------------" + << std::endl + << "Final Runtime (seconds): " << runtime + << std::endl + << "Updates executed: " << engine.num_updates() << std::endl + << "Update Rate (updates/second): " + << engine.num_updates() / runtime << std::endl; const double total_rank = graph.map_reduce_vertices(map_rank); std::cout << "Total rank: " << total_rank << std::endl; @@ -273,9 +279,6 @@ int main(int argc, char** argv) { false); // do not save edges } - double totalpr = graph.map_reduce_vertices(pagerank_sum); - std::cout << "Totalpr = " << totalpr << "\n"; - // Tear-down communication layer and quit ----------------------------------- graphlab::mpi_tools::finalize(); return EXIT_SUCCESS; From e3abf05ee3985840538d7ba824589a7fef53513e Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Sun, 17 Nov 2013 23:47:39 +0800 Subject: [PATCH 14/50] fix concurrent loading multiple file bug in standalone mode --- .../ingress/distributed_hybrid_ginger_ingress.hpp | 10 ++++++++-- .../graph/ingress/distributed_hybrid_ingress.hpp | 9 +++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp index a70a606b4c..eac4a18074 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -144,6 +145,7 @@ namespace graphlab { bool standalone; std::vector hybrid_edges; + simple_spinlock hybrid_edges_lock; //these are used for edges balance buffered_exchange< proc_edges_pair_type > proc_edges_exchange; @@ -196,7 +198,9 @@ namespace graphlab { const edge_buffer_record record(source, target, edata); if (standalone) { /* Fast pass for standalone case. */ + hybrid_edges_lock.lock(); hybrid_edges.push_back(record); + hybrid_edges_lock.unlock(); } else { const procid_t owning_proc = graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); @@ -213,10 +217,12 @@ namespace graphlab { const std::vector& edatas) { if (standalone) { /* fast pass for standalone case. */ + hybrid_edges_lock.lock(); for(size_t i = 0; i < in.size();i++){ const edge_buffer_record record(in[i], out, edatas[i]); hybrid_edges.push_back(record); - } + } + hybrid_edges_lock.unlock(); } else { procid_t owning_proc = 0; if (in.size() >= threshold) { @@ -334,7 +340,7 @@ namespace graphlab { size_t nprocs = hybrid_rpc.numprocs(); procid_t l_procid = hybrid_rpc.procid(); - size_t nedges; + size_t nedges = 0; if (l_procid == 0) { memory_info::log_usage("start finalizing"); diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp index 8f6100f2d3..4e451244c1 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -88,7 +89,7 @@ namespace graphlab { vertex_buffer_type; std::vector hybrid_edges; - + simple_spinlock hybrid_edges_lock; /// one-by-one ingress. e.g., SNAP buffered_exchange hybrid_edge_exchange; @@ -143,7 +144,9 @@ namespace graphlab { const edge_buffer_record record(source, target, edata); if (standalone) { /* Fast pass for standalone case. */ + hybrid_edges_lock.lock(); hybrid_edges.push_back(record); + hybrid_edges_lock.unlock(); } else { const procid_t owning_proc = graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); @@ -161,10 +164,12 @@ namespace graphlab { const std::vector& edatas) { if (standalone) { /* fast pass for standalone case. */ + hybrid_edges_lock.lock(); for(size_t i = 0; i < sources.size();i++){ const edge_buffer_record record(sources[i], target, edatas[i]); hybrid_edges.push_back(record); - } + } + hybrid_edges_lock.unlock(); } else { const procid_t target_owner_proc = graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); From f9576d77bcb3e59a03a57c749a7b84209686db53 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Mon, 18 Nov 2013 17:15:22 +0800 Subject: [PATCH 15/50] minor change to threshold setting --- .../graph/ingress/distributed_hybrid_ginger_ingress.hpp | 4 ++-- src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp index eac4a18074..6d49548858 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp @@ -225,7 +225,7 @@ namespace graphlab { hybrid_edges_lock.unlock(); } else { procid_t owning_proc = 0; - if (in.size() >= threshold) { + if (in.size() > threshold) { // TODO: no need send, just resend latter owning_proc = graph_hash::hash_vertex(out) % base_type::rpc.numprocs(); for (size_t i = 0; i < in.size(); ++i) { @@ -511,7 +511,7 @@ namespace graphlab { for (size_t lvid = 0; lvid < graph.num_local_vertices(); lvid++) { vertex_record& vrec = graph.lvid2record[lvid]; - if (vrec.num_in_edges >= threshold) { + if (vrec.num_in_edges > threshold) { if (vrec.owner == l_procid) { vrec.type = graph_type::HIGH_MASTER; high_master ++; diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp index 4e451244c1..cd825db0fb 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp @@ -174,7 +174,7 @@ namespace graphlab { const procid_t target_owner_proc = graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); - if(sources.size() >= threshold){ + if(sources.size() > threshold){ std::vector batch_rec_vector(hybrid_rpc.numprocs()); for (size_t i = 0; i < sources.size(); i++){ @@ -423,7 +423,7 @@ namespace graphlab { for (size_t lvid = 0; lvid < graph.num_local_vertices(); lvid++) { vertex_record& vrec = graph.lvid2record[lvid]; - if (vrec.num_in_edges >= threshold) { + if (vrec.num_in_edges > threshold) { if (vrec.owner == l_procid) { vrec.type = graph_type::HIGH_MASTER; high_master ++; From f533bd74d66424c5a0427dc6c3df7a7449a83562 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Wed, 20 Nov 2013 12:49:45 +0800 Subject: [PATCH 16/50] refine output --- src/graphlab/engine/powerlyra_sync_engine.hpp | 50 ++++---- src/graphlab/engine/synchronous_engine.hpp | 114 ++++++++++++++++-- .../distributed_hybrid_ginger_ingress.hpp | 6 +- .../ingress/distributed_hybrid_ingress.hpp | 4 +- 4 files changed, 137 insertions(+), 37 deletions(-) diff --git a/src/graphlab/engine/powerlyra_sync_engine.hpp b/src/graphlab/engine/powerlyra_sync_engine.hpp index 5b27dcd77e..6501a62898 100644 --- a/src/graphlab/engine/powerlyra_sync_engine.hpp +++ b/src/graphlab/engine/powerlyra_sync_engine.hpp @@ -60,7 +60,7 @@ #define ALIGN_DOWN(_n, _w) ((_n) & (~((_w)-1))) -#undef TUNING +#define TUNING namespace graphlab { @@ -592,13 +592,13 @@ namespace graphlab { /** * \brief The tetrad type used to update vertex data and activate mirrors. */ - typedef tetrad vid_vdata_vprog_edir_tetrad_type; + typedef tetrad vid_vdata_edir_vprog_tetrad_type; /** * \brief The type of the express used to update mirrors */ - typedef fiber_buffered_exchange + typedef fiber_buffered_exchange update_exchange_type; /** @@ -1558,13 +1558,12 @@ namespace graphlab { all_compute_time_vec[rmi.procid()] = total_compute_time; rmi.all_gather(all_compute_time_vec); -#ifdef TUNING - logstream(LOG_INFO) << "Local Calls(G|A|S): " + /*logstream(LOG_INFO) << "Local Calls(G|A|S): " << completed_gathers.value << "|" << completed_applys.value << "|" << completed_scatters.value << std::endl; -#endif + */ size_t global_completed = completed_applys; rmi.all_reduce(global_completed); @@ -1795,8 +1794,8 @@ namespace graphlab { accum = vprog.gather(context, vertex, edge); accum_is_set = true; } - ++edges_touched; } + ++edges_touched; } // end of if in_edges/all_edges // Loop over out edges if(gather_dir == OUT_EDGES || gather_dir == ALL_EDGES) { @@ -1808,8 +1807,8 @@ namespace graphlab { accum = vprog.gather(context, vertex, edge); accum_is_set = true; } - ++edges_touched; - } + } + ++edges_touched; } // end of if out_edges/all_edges INCREMENT_EVENT(EVENT_GATHERS, edges_touched); ++ngather_inc; @@ -1836,9 +1835,9 @@ namespace graphlab { } } // end of loop over vertices to compute gather accumulators completed_gathers += ngather_inc; + per_thread_compute_time[thread_id] += ti.current_time(); accum_exchange.partial_flush(); thread_barrier.wait(); - per_thread_compute_time[thread_id] += ti.current_time(); if(thread_id == 0) accum_exchange.flush(); // full_barrier thread_barrier.wait(); recv_accums(); @@ -1890,16 +1889,19 @@ namespace graphlab { if (edge_dirs[lvid] != graphlab::NO_EDGES) active_minorstep.set_bit(lvid); - // send Ax1 and Sx1 msgs - send_updates(lvid, thread_id); + else + vertex_programs[lvid] = vertex_program_type(); + // send Ax1 and Sx1 + send_updates(lvid, thread_id); + if(++vcount % TRY_RECV_MOD == 0) recv_updates(); } } // end of loop over vertices to run apply completed_applys += napply_inc; + per_thread_compute_time[thread_id] += ti.current_time(); update_exchange.partial_flush(); thread_barrier.wait(); - per_thread_compute_time[thread_id] += ti.current_time(); // Flush the buffer and finish receiving any remaining updates. if(thread_id == 0) update_exchange.flush(); // full_barrier thread_barrier.wait(); @@ -1942,16 +1944,16 @@ namespace graphlab { foreach(local_edge_type local_edge, local_vertex.in_edges()) { edge_type edge(local_edge); vprog.scatter(context, vertex, edge); - ++edges_touched; } + ++edges_touched; } // end of if in_edges/all_edges // Loop over out edges if(scatter_dir == OUT_EDGES || scatter_dir == ALL_EDGES) { foreach(local_edge_type local_edge, local_vertex.out_edges()) { edge_type edge(local_edge); vprog.scatter(context, vertex, edge); - ++edges_touched; } + ++edges_touched; } // end of if out_edges/all_edges INCREMENT_EVENT(EVENT_SCATTERS, edges_touched); // Clear the vertex program @@ -2003,10 +2005,10 @@ namespace graphlab { local_vertex_type vertex = graph.l_vertex(lvid); foreach(const procid_t& mirror, vertex.mirrors()) { update_exchange.send(mirror, - make_tetrad(vid, - vertex.data(), - vertex_programs[lvid], - edge_dirs[lvid])); + make_tetrad(vid, + vertex.data(), + edge_dirs[lvid], + vertex_programs[lvid])); } } // end of send_update @@ -2017,13 +2019,13 @@ namespace graphlab { while(update_exchange.recv(recv_buffer)) { for (size_t i = 0;i < recv_buffer.size(); ++i) { typename update_exchange_type::buffer_type& buffer = recv_buffer[i].buffer; - foreach(const vid_vdata_vprog_edir_tetrad_type& t, buffer) { + foreach(const vid_vdata_edir_vprog_tetrad_type& t, buffer) { const lvid_type lvid = graph.local_vid(t.first); ASSERT_FALSE(graph.l_is_master(lvid)); graph.l_vertex(lvid).data() = t.second; - if (t.fourth != graphlab::NO_EDGES) { - vertex_programs[lvid] = t.third; - edge_dirs[lvid] = t.fourth; + if (t.third != graphlab::NO_EDGES) { + edge_dirs[lvid] = t.third; + vertex_programs[lvid] = t.fourth; active_minorstep.set_bit(lvid); } } diff --git a/src/graphlab/engine/synchronous_engine.hpp b/src/graphlab/engine/synchronous_engine.hpp index 6331282b55..2843e53e7a 100644 --- a/src/graphlab/engine/synchronous_engine.hpp +++ b/src/graphlab/engine/synchronous_engine.hpp @@ -56,6 +56,7 @@ #include +#define TUNING namespace graphlab { @@ -394,6 +395,36 @@ namespace graphlab { */ float start_time; + /** + * \brief The total execution time. + */ + double exec_time; + + /** + * \brief The time spends on exch-msgs phase. + */ + double exch_time; + + /** + * \brief The time spends on recv-msgs phase. + */ + double recv_time; + + /** + * \brief The time spends on gather phase. + */ + double gather_time; + + /** + * \brief The time spends on apply phase. + */ + double apply_time; + + /** + * \brief The time spends on scatter phase. + */ + double scatter_time; + /** * \brief The timeout time in seconds */ @@ -503,11 +534,21 @@ namespace graphlab { */ dense_bitset active_minorstep; + /** + * \brief A counter measuring the number of gathers that have been completed + */ + atomic completed_gathers; + /** * \brief A counter measuring the number of applys that have been completed */ atomic completed_applys; + /** + * \brief A counter measuring the number of scatters that have been completed + */ + atomic completed_scatters; + /** * \brief The shared counter used coordinate operations between @@ -1073,7 +1114,9 @@ namespace graphlab { // Clear up force_abort = false; iteration_counter = 0; + completed_gathers = 0; completed_applys = 0; + completed_scatters = 0; has_message.clear(); has_gather_accum.clear(); has_cache.clear(); @@ -1279,6 +1322,9 @@ namespace graphlab { // Start the timer graphlab::timer timer; timer.start(); start_time = timer::approx_time_seconds(); + exec_time = exch_time = recv_time = + gather_time = apply_time = scatter_time = 0.0; + graphlab::timer ti, bk_ti; iteration_counter = 0; force_abort = false; execution_status::status_enum termination_reason = @@ -1300,6 +1346,7 @@ namespace graphlab { << std::endl; } // Program Main loop ==================================================== + ti.start(); while(iteration_counter < max_iterations && !force_abort ) { // Check first to see if we are out of time @@ -1311,7 +1358,7 @@ namespace graphlab { bool print_this_round = (elapsed_seconds() - last_print) >= 5; if(rmi.procid() == 0 && print_this_round) { - logstream(LOG_EMPH) + logstream(LOG_DEBUG) << rmi.procid() << ": Starting iteration: " << iteration_counter << std::endl; last_print = elapsed_seconds(); @@ -1326,7 +1373,9 @@ namespace graphlab { // Exchange Messages -------------------------------------------------- // Exchange any messages in the local message vectors // if (rmi.procid() == 0) std::cout << "Exchange messages..." << std::endl; + bk_ti.start(); run_synchronous( &synchronous_engine::exchange_messages ); + exch_time += bk_ti.current_time(); /** * Post conditions: * 1) only master vertices have messages @@ -1339,11 +1388,13 @@ namespace graphlab { // if (rmi.procid() == 0) std::cout << "Receive messages..." << std::endl; num_active_vertices = 0; + bk_ti.start(); run_synchronous( &synchronous_engine::receive_messages ); if (sched_allv) { active_minorstep.fill(); } has_message.clear(); + recv_time += bk_ti.current_time(); /** * Post conditions: * 1) there are no messages remaining @@ -1372,11 +1423,13 @@ namespace graphlab { // Execute the gather operation for all vertices that are active // in this minor-step (active-minorstep bit set). // if (rmi.procid() == 0) std::cout << "Gathering..." << std::endl; + bk_ti.start(); run_synchronous( &synchronous_engine::execute_gathers ); // Clear the minor step bit since only super-step vertices // (only master vertices are required to participate in the // apply step) active_minorstep.clear(); // rmi.barrier(); + gather_time += bk_ti.current_time(); /** * Post conditions: * 1) gather_accum for all master vertices contains the @@ -1388,7 +1441,9 @@ namespace graphlab { // Execute Apply Operations ------------------------------------------- // Run the apply function on all active vertices // if (rmi.procid() == 0) std::cout << "Applying..." << std::endl; + bk_ti.start(); run_synchronous( &synchronous_engine::execute_applys ); + apply_time += bk_ti.current_time(); /** * Post conditions: * 1) any changes to the vertex data have been synchronized @@ -1403,13 +1458,15 @@ namespace graphlab { // Execute Scatter Operations ----------------------------------------- // Execute each of the scatters on all minor-step active vertices. + bk_ti.start(); run_synchronous( &synchronous_engine::execute_scatters ); + scatter_time += bk_ti.current_time(); /** * Post conditions: * 1) NONE */ if(rmi.procid() == 0 && print_this_round) - logstream(LOG_EMPH) << "\t Running Aggregators" << std::endl; + logstream(LOG_DEBUG) << "\t Running Aggregators" << std::endl; // probe the aggregator aggregator.tick_synchronous(); @@ -1419,6 +1476,7 @@ namespace graphlab { graph.save_binary(snapshot_path); } } + exec_time = ti.current_time(); if (rmi.procid() == 0) { logstream(LOG_EMPH) << iteration_counter @@ -1433,16 +1491,50 @@ namespace graphlab { all_compute_time_vec[rmi.procid()] = total_compute_time; rmi.all_gather(all_compute_time_vec); + /*logstream(LOG_INFO) << "Local Calls(G|A|S): " + << completed_gathers.value << "|" + << completed_applys.value << "|" + << completed_scatters.value + << std::endl; + */ + size_t global_completed = completed_applys; rmi.all_reduce(global_completed); completed_applys = global_completed; rmi.cout() << "Updates: " << completed_applys.value << "\n"; + +#ifdef TUNING + global_completed = completed_gathers; + rmi.all_reduce(global_completed); + completed_gathers = global_completed; + + global_completed = completed_scatters; + rmi.all_reduce(global_completed); + completed_scatters = global_completed; +#endif + if (rmi.procid() == 0) { +#ifdef TUNING + logstream(LOG_INFO) << "Total Calls(G|A|S): " + << completed_gathers.value << "|" + << completed_applys.value << "|" + << completed_scatters.value + << std::endl; +#endif logstream(LOG_INFO) << "Compute Balance: "; for (size_t i = 0;i < all_compute_time_vec.size(); ++i) { logstream(LOG_INFO) << all_compute_time_vec[i] << " "; } logstream(LOG_INFO) << std::endl; + logstream(LOG_EMPH) << " Execution Time: " << exec_time << std::endl; + logstream(LOG_EMPH) << "Breakdown(X|R|G|A|S): " + << exch_time << "|" + << recv_time << "|" + << gather_time << "|" + << apply_time << "|" + << scatter_time + << std::endl; + } rmi.full_barrier(); // Stop the aggregator @@ -1566,6 +1658,7 @@ namespace graphlab { const size_t TRY_RECV_MOD = 1000; size_t vcount = 0; const bool caching_enabled = !gather_cache.empty(); + size_t ngather_inc = 0; timer ti; fixed_dense_bitset<8 * sizeof(size_t)> local_bitset; // a word-size = 64 bit @@ -1630,8 +1723,9 @@ namespace graphlab { // elocks[local_edge.id()].unlock(); ++edges_touched; } - INCREMENT_EVENT(EVENT_GATHERS, edges_touched); } // end of if out_edges/all_edges + INCREMENT_EVENT(EVENT_GATHERS, edges_touched); + ++ngather_inc; vprog.post_local_gather(accum); // If caching is enabled then save the accumulator to the // cache for future iterations. Note that it is possible @@ -1652,10 +1746,11 @@ namespace graphlab { // try to recv gathers if there are any in the buffer if(++vcount % TRY_RECV_MOD == 0) recv_gathers(); } - } // end of loop over vertices to compute gather accumulators + } // end of loop over vertices to compute gather accumulators + completed_gathers += ngather_inc; per_thread_compute_time[thread_id] += ti.current_time(); gather_exchange.partial_flush(); - // Finish sending and receiving all gather operations + // Finish sending and receiving all gather operations thread_barrier.wait(); if(thread_id == 0) gather_exchange.flush(); thread_barrier.wait(); @@ -1669,6 +1764,7 @@ namespace graphlab { context_type context(*this, graph); const size_t TRY_RECV_MOD = 1000; size_t vcount = 0; + size_t napply_inc = 0; timer ti; fixed_dense_bitset<8 * sizeof(size_t)> local_bitset; // allocate a word size = 64bits @@ -1696,7 +1792,7 @@ namespace graphlab { INCREMENT_EVENT(EVENT_APPLIES, 1); vertex_programs[lvid].apply(context, vertex, accum); // record an apply as a completed task - ++completed_applys; + ++napply_inc; // Clear the accumulator to save some memory gather_accum[lvid] = gather_type(); // synchronize the changed vertex data with all mirrors @@ -1718,7 +1814,7 @@ namespace graphlab { } } } // end of loop over vertices to run apply - + completed_applys += napply_inc; per_thread_compute_time[thread_id] += ti.current_time(); vprog_exchange.partial_flush(); vdata_exchange.partial_flush(); @@ -1739,6 +1835,7 @@ namespace graphlab { void synchronous_engine:: execute_scatters(const size_t thread_id) { context_type context(*this, graph); + size_t nscatter_inc = 0; timer ti; fixed_dense_bitset<8 * sizeof(size_t)> local_bitset; // allocate a word size = 64 bits while (1) { @@ -1784,9 +1881,10 @@ namespace graphlab { INCREMENT_EVENT(EVENT_SCATTERS, edges_touched); // Clear the vertex program vertex_programs[lvid] = vertex_program_type(); + ++nscatter_inc; } // end of if active on this minor step } // end of loop over vertices to complete scatter operation - + completed_scatters += nscatter_inc; per_thread_compute_time[thread_id] += ti.current_time(); } // end of execute_scatters diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp index 6d49548858..c14e4c2bf0 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp @@ -288,7 +288,7 @@ namespace graphlab { proc_edges_exchange.clear(); for (int i=0; i 0) logstream(LOG_EMPH) << "hybrid info: mirror [" << high_mirror << " " << low_mirror << " " - << ((high_mirror*1.0)/(high_mirror+low_mirror)) << "]" + << (float(high_mirror)/(high_mirror+low_mirror)) << "]" << std::endl; memory_info::log_usage("set vertex type done."); diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp index cd825db0fb..418030a3e9 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp @@ -470,13 +470,13 @@ namespace graphlab { logstream(LOG_EMPH) << "hybrid info: master [" << high_master << " " << low_master << " " - << ((high_master*1.0)/(high_master+low_master)) << "]" + << (float(high_master)/(high_master+low_master)) << "]" << std::endl; if ((high_mirror + low_mirror) > 0) logstream(LOG_EMPH) << "hybrid info: mirror [" << high_mirror << " " << low_mirror << " " - << ((high_mirror*1.0)/(high_mirror+low_mirror)) << "]" + << (float(high_mirror)/(high_mirror+low_mirror)) << "]" << std::endl; memory_info::log_usage("set vertex type done."); From af8bd647c8611f253171bb7e47f36268c7b31269 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Fri, 22 Nov 2013 06:29:14 +0800 Subject: [PATCH 17/50] [bug] miss gathering of low-degree vertices when OUT_EDGES/ALL_EDGES --- src/graphlab/engine/powerlyra_sync_engine.hpp | 17 +++++++---------- src/graphlab/engine/synchronous_engine.hpp | 13 ++++++------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/graphlab/engine/powerlyra_sync_engine.hpp b/src/graphlab/engine/powerlyra_sync_engine.hpp index 6501a62898..468741615c 100644 --- a/src/graphlab/engine/powerlyra_sync_engine.hpp +++ b/src/graphlab/engine/powerlyra_sync_engine.hpp @@ -1562,9 +1562,8 @@ namespace graphlab { << completed_gathers.value << "|" << completed_applys.value << "|" << completed_scatters.value - << std::endl; - */ - + << std::endl;*/ + size_t global_completed = completed_applys; rmi.all_reduce(global_completed); completed_applys = global_completed; @@ -1766,8 +1765,6 @@ namespace graphlab { if (lvid >= graph.num_local_vertices()) break; // [TARGET]: High/Low-degree Masters, and High/Low-degree Mirrors - if (low_mirror_lvid(lvid)) continue; - bool accum_is_set = false; gather_type accum = gather_type(); // if caching is enabled and we have a cache entry then use @@ -1794,8 +1791,8 @@ namespace graphlab { accum = vprog.gather(context, vertex, edge); accum_is_set = true; } + ++edges_touched; } - ++edges_touched; } // end of if in_edges/all_edges // Loop over out edges if(gather_dir == OUT_EDGES || gather_dir == ALL_EDGES) { @@ -1807,8 +1804,8 @@ namespace graphlab { accum = vprog.gather(context, vertex, edge); accum_is_set = true; } - } - ++edges_touched; + ++edges_touched; + } } // end of if out_edges/all_edges INCREMENT_EVENT(EVENT_GATHERS, edges_touched); ++ngather_inc; @@ -1944,16 +1941,16 @@ namespace graphlab { foreach(local_edge_type local_edge, local_vertex.in_edges()) { edge_type edge(local_edge); vprog.scatter(context, vertex, edge); + ++edges_touched; } - ++edges_touched; } // end of if in_edges/all_edges // Loop over out edges if(scatter_dir == OUT_EDGES || scatter_dir == ALL_EDGES) { foreach(local_edge_type local_edge, local_vertex.out_edges()) { edge_type edge(local_edge); vprog.scatter(context, vertex, edge); + ++edges_touched; } - ++edges_touched; } // end of if out_edges/all_edges INCREMENT_EVENT(EVENT_SCATTERS, edges_touched); // Clear the vertex program diff --git a/src/graphlab/engine/synchronous_engine.hpp b/src/graphlab/engine/synchronous_engine.hpp index 2843e53e7a..8b43fdd058 100644 --- a/src/graphlab/engine/synchronous_engine.hpp +++ b/src/graphlab/engine/synchronous_engine.hpp @@ -56,7 +56,7 @@ #include -#define TUNING +#undef TUNING namespace graphlab { @@ -1495,9 +1495,8 @@ namespace graphlab { << completed_gathers.value << "|" << completed_applys.value << "|" << completed_scatters.value - << std::endl; - */ - + << std::endl;*/ + size_t global_completed = completed_applys; rmi.all_reduce(global_completed); completed_applys = global_completed; @@ -1709,7 +1708,7 @@ namespace graphlab { // elocks[local_edge.id()].unlock(); } } // end of if in_edges/all_edges - // Loop over out edges + // Loop over out edges if(gather_dir == OUT_EDGES || gather_dir == ALL_EDGES) { foreach(local_edge_type local_edge, local_vertex.out_edges()) { edge_type edge(local_edge); @@ -1865,8 +1864,8 @@ namespace graphlab { // elocks[local_edge.id()].lock(); vprog.scatter(context, vertex, edge); // elocks[local_edge.id()].unlock(); + ++edges_touched; } - ++edges_touched; } // end of if in_edges/all_edges // Loop over out edges if(scatter_dir == OUT_EDGES || scatter_dir == ALL_EDGES) { @@ -1875,8 +1874,8 @@ namespace graphlab { // elocks[local_edge.id()].lock(); vprog.scatter(context, vertex, edge); // elocks[local_edge.id()].unlock(); + ++edges_touched; } - ++edges_touched; } // end of if out_edges/all_edges INCREMENT_EVENT(EVENT_SCATTERS, edges_touched); // Clear the vertex program From afde7edf6be2e49e41246368a53b4f4e2b36db59 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Fri, 22 Nov 2013 06:30:51 +0800 Subject: [PATCH 18/50] [bug] unmatch threshold > and >=. [perf] avoid contention on add_edge when parallel loading --- src/graphlab/graph/distributed_graph.hpp | 44 +++-- .../distributed_hybrid_ginger_ingress.hpp | 6 +- .../ingress/distributed_hybrid_ingress.hpp | 181 ++++++++++-------- 3 files changed, 128 insertions(+), 103 deletions(-) diff --git a/src/graphlab/graph/distributed_graph.hpp b/src/graphlab/graph/distributed_graph.hpp index 306e0e23f3..76a63c13ab 100644 --- a/src/graphlab/graph/distributed_graph.hpp +++ b/src/graphlab/graph/distributed_graph.hpp @@ -2246,29 +2246,31 @@ namespace graphlab { if (get_cuts_type() == HYBRID_GINGER_CUTS) { for(size_t i = 0; i < graph_files.size(); ++i) { - if ((parallel_ingress && (i % rpc.numprocs() == rpc.procid())) - || (!parallel_ingress && (rpc.procid() == 0))) { - logstream(LOG_EMPH) << "Loading graph from file: " << graph_files[i] << std::endl; - // is it a gzip file ? - const bool gzip = boost::ends_with(graph_files[i], ".gz"); - // open the stream - std::ifstream in_file(graph_files[i].c_str(), - std::ios_base::in | std::ios_base::binary); - // attach gzip if the file is gzip - boost::iostreams::filtering_stream fin; - // Using gzip filter - if (gzip) fin.push(boost::iostreams::gzip_decompressor()); - fin.push(in_file); - const bool success = load_from_stream(graph_files[i], fin, line_parser); - if(!success) { - logstream(LOG_FATAL) - << "\n\tError parsing file: " << graph_files[i] << std::endl; + if ((parallel_ingress && (i % rpc.numprocs() == rpc.procid())) + || (!parallel_ingress && (rpc.procid() == 0))) { + logstream(LOG_EMPH) << "Loading graph from file (sequential): " + << graph_files[i] << std::endl; + // is it a gzip file ? + const bool gzip = boost::ends_with(graph_files[i], ".gz"); + // open the stream + std::ifstream in_file(graph_files[i].c_str(), + std::ios_base::in | std::ios_base::binary); + // attach gzip if the file is gzip + boost::iostreams::filtering_stream fin; + // Using gzip filter + if (gzip) fin.push(boost::iostreams::gzip_decompressor()); + fin.push(in_file); + const bool success = load_from_stream(graph_files[i], fin, line_parser); + if(!success) { + logstream(LOG_FATAL) + << "\n\tError parsing file: " << graph_files[i] << std::endl; + } + fin.pop(); + if (gzip) fin.pop(); } - fin.pop(); - if (gzip) fin.pop(); } - } - } else { + } + else { #ifdef _OPENMP #pragma omp parallel for #endif diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp index c14e4c2bf0..d0b600eb3e 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp @@ -145,7 +145,6 @@ namespace graphlab { bool standalone; std::vector hybrid_edges; - simple_spinlock hybrid_edges_lock; //these are used for edges balance buffered_exchange< proc_edges_pair_type > proc_edges_exchange; @@ -198,9 +197,8 @@ namespace graphlab { const edge_buffer_record record(source, target, edata); if (standalone) { /* Fast pass for standalone case. */ - hybrid_edges_lock.lock(); + // NOTE: forbidden loading in parallel hybrid_edges.push_back(record); - hybrid_edges_lock.unlock(); } else { const procid_t owning_proc = graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); @@ -217,12 +215,10 @@ namespace graphlab { const std::vector& edatas) { if (standalone) { /* fast pass for standalone case. */ - hybrid_edges_lock.lock(); for(size_t i = 0; i < in.size();i++){ const edge_buffer_record record(in[i], out, edatas[i]); hybrid_edges.push_back(record); } - hybrid_edges_lock.unlock(); } else { procid_t owning_proc = 0; if (in.size() > threshold) { diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp index 418030a3e9..9d741f0535 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp @@ -40,7 +40,7 @@ #include -#undef TUNING +#define TUNING namespace graphlab { template class distributed_graph; @@ -68,8 +68,8 @@ namespace graphlab { typedef typename buffered_exchange::buffer_type vertex_id_buffer_type; - typedef typename graph_type::zone_type zone_type; - + typedef typename graph_type::zone_type zone_type; + /// The rpc interface for this object dc_dist_object hybrid_rpc; /// The underlying distributed graph object that is being loaded @@ -89,7 +89,6 @@ namespace graphlab { vertex_buffer_type; std::vector hybrid_edges; - simple_spinlock hybrid_edges_lock; /// one-by-one ingress. e.g., SNAP buffered_exchange hybrid_edge_exchange; @@ -126,8 +125,18 @@ namespace graphlab { graph_type& graph, size_t threshold = 100) : base_type(dc, graph), hybrid_rpc(dc, this), graph(graph), threshold(threshold), - hybrid_edge_exchange(dc), hybrid_vertex_exchange(dc), - high_batch_edge_exchange(dc), low_batch_edge_exchange(dc){ +#ifdef _OPENMP + hybrid_edge_exchange(dc, omp_get_max_threads()), + hybrid_vertex_exchange(dc, omp_get_max_threads()), + high_batch_edge_exchange(dc, omp_get_max_threads()), + low_batch_edge_exchange(dc, omp_get_max_threads()) +#else + hybrid_edge_exchange(dc), + hybrid_vertex_exchange(dc), + high_batch_edge_exchange(dc), + low_batch_edge_exchange(dc) +#endif + { /* fast pass for standalone case. */ standalone = hybrid_rpc.numprocs() == 1; hybrid_rpc.barrier(); @@ -141,17 +150,14 @@ namespace graphlab { */ void add_edge(vertex_id_type source, vertex_id_type target, const EdgeData& edata) { - const edge_buffer_record record(source, target, edata); - if (standalone) { - /* Fast pass for standalone case. */ - hybrid_edges_lock.lock(); - hybrid_edges.push_back(record); - hybrid_edges_lock.unlock(); - } else { - const procid_t owning_proc = - graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); - hybrid_edge_exchange.send(owning_proc, record); - } + const edge_buffer_record record(source, target, edata); + const procid_t owning_proc = standalone ? 0 : + graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); +#ifdef _OPENMP + hybrid_edge_exchange.send(owning_proc, record, omp_get_thread_num()); +#else + hybrid_edge_exchange.send(owning_proc, record); +#endif } // end of add edge @@ -164,12 +170,12 @@ namespace graphlab { const std::vector& edatas) { if (standalone) { /* fast pass for standalone case. */ - hybrid_edges_lock.lock(); - for(size_t i = 0; i < sources.size();i++){ - const edge_buffer_record record(sources[i], target, edatas[i]); - hybrid_edges.push_back(record); - } - hybrid_edges_lock.unlock(); + const batch_edge_buffer_record record(sources, target, edatas); +#ifdef _OPENMP + low_batch_edge_exchange.send(0, record, omp_get_thread_num()); +#else + low_batch_edge_exchange.send(0, record); +#endif } else { const procid_t target_owner_proc = graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); @@ -187,13 +193,20 @@ namespace graphlab { for (size_t i = 0; i < batch_rec_vector.size(); i++) { if(batch_rec_vector[i].sources.size() > 0){ batch_rec_vector[i].target=target; +#ifdef _OPENMP + high_batch_edge_exchange.send((procid_t)i, batch_rec_vector[i], omp_get_thread_num()); +#else high_batch_edge_exchange.send((procid_t)i, batch_rec_vector[i]); +#endif } } - } - else{ + } else{ const batch_edge_buffer_record record(sources, target, edatas); +#ifdef _OPENMP + low_batch_edge_exchange.send(target_owner_proc, record, omp_get_thread_num()); +#else low_batch_edge_exchange.send(target_owner_proc, record); +#endif } } } // end of add edges @@ -236,7 +249,21 @@ namespace graphlab { /**************************************************************************/ { if (standalone) { - nedges = hybrid_edges.size(); + /* fast pass for redundant finalization with no graph changes. */ + hybrid_edge_exchange.flush(); + if (hybrid_edge_exchange.size() > 0) { + edge_buffer_type edge_buffer; + procid_t proc = -1; + + nedges = hybrid_edge_exchange.size(); + hybrid_edges.reserve(nedges); + + while(hybrid_edge_exchange.recv(proc, edge_buffer)) + foreach(const edge_buffer_record& rec, edge_buffer) + hybrid_edges.push_back(rec); + + hybrid_edge_exchange.clear(); + } } else { hopscotch_map in_degree_set; edge_buffer_type edge_buffer; @@ -256,7 +283,7 @@ namespace graphlab { } } hybrid_edge_exchange.clear(); - hybrid_edge_exchange.barrier(); // sync before reusing + hybrid_rpc.full_barrier(); // sync before reusing #ifdef TUNING if(l_procid == 0) { memory_info::log_usage("save local edges and count in-degree done."); @@ -270,7 +297,7 @@ namespace graphlab { // re-send edges of high-degree vertices for (size_t i = 0; i < hybrid_edges.size(); i++) { edge_buffer_record& rec = hybrid_edges[i]; - if (in_degree_set[rec.target] >= threshold) { + if (in_degree_set[rec.target] > threshold) { const procid_t source_owner_proc = graph_hash::hash_vertex(rec.source) % nprocs; if(source_owner_proc != l_procid){ @@ -319,64 +346,64 @@ namespace graphlab { #endif } + } - /* collect edges for batch ingress (e.g. RADJ) */ - // store edges of low-degree vertices into hybrid_edges - low_batch_edge_exchange.flush(); - if (low_batch_edge_exchange.size() > 0) { - batch_edge_buffer_type batch_edge_buffer; - proc = -1; - while(low_batch_edge_exchange.recv(proc, batch_edge_buffer)) { - foreach(const batch_edge_buffer_record& batch_rec, batch_edge_buffer) { - nedges += batch_rec.sources.size(); - for(size_t i = 0; i < batch_rec.sources.size();i++){ - edge_buffer_record rec(batch_rec.sources[i], - batch_rec.target, - batch_rec.edatas[i]); - hybrid_edges.push_back(rec); - } + /* collect edges for batch ingress (e.g. RADJ) */ + // store edges of low-degree vertices into hybrid_edges + low_batch_edge_exchange.flush(); + if (low_batch_edge_exchange.size() > 0) { + batch_edge_buffer_type batch_edge_buffer; + procid_t proc = -1; + while(low_batch_edge_exchange.recv(proc, batch_edge_buffer)) { + foreach(const batch_edge_buffer_record& batch_rec, batch_edge_buffer) { + nedges += batch_rec.sources.size(); + for(size_t i = 0; i < batch_rec.sources.size();i++){ + edge_buffer_record rec(batch_rec.sources[i], + batch_rec.target, + batch_rec.edatas[i]); + hybrid_edges.push_back(rec); } } - low_batch_edge_exchange.clear(); + } + low_batch_edge_exchange.clear(); #ifdef TUNING - if(l_procid == 0) { - memory_info::log_usage("receive low-degree edges done."); - logstream(LOG_EMPH) << "receive low-degree edges: " - << ti.current_time() - << " secs" - << std::endl; - } + if(l_procid == 0) { + memory_info::log_usage("receive low-degree edges done."); + logstream(LOG_EMPH) << "receive low-degree edges: " + << ti.current_time() + << " secs" + << std::endl; + } #endif - } + } - // store edges of high-degree vertices into hybrid_edges - high_batch_edge_exchange.flush(); - if (high_batch_edge_exchange.size() > 0) { - batch_edge_buffer_type batch_edge_buffer; - procid_t proc = -1; - while(high_batch_edge_exchange.recv(proc, batch_edge_buffer)) { - foreach(const batch_edge_buffer_record& batch_rec, batch_edge_buffer) { - nedges += batch_rec.sources.size(); - for(size_t i = 0; i < batch_rec.sources.size(); i++){ - edge_buffer_record rec(batch_rec.sources[i], - batch_rec.target, - batch_rec.edatas[i]); - hybrid_edges.push_back(rec); - } + // store edges of high-degree vertices into hybrid_edges + high_batch_edge_exchange.flush(); + if (high_batch_edge_exchange.size() > 0) { + batch_edge_buffer_type batch_edge_buffer; + procid_t proc = -1; + while(high_batch_edge_exchange.recv(proc, batch_edge_buffer)) { + foreach(const batch_edge_buffer_record& batch_rec, batch_edge_buffer) { + nedges += batch_rec.sources.size(); + for(size_t i = 0; i < batch_rec.sources.size(); i++){ + edge_buffer_record rec(batch_rec.sources[i], + batch_rec.target, + batch_rec.edatas[i]); + hybrid_edges.push_back(rec); } } - high_batch_edge_exchange.clear(); + } + high_batch_edge_exchange.clear(); #ifdef TUNING - if(l_procid == 0) { - memory_info::log_usage("receive high-degree edges done."); - logstream(LOG_EMPH) << "receive high-degree edges: " - << ti.current_time() - << " secs" - << std::endl; - } -#endif + if(l_procid == 0) { + memory_info::log_usage("receive high-degree edges done."); + logstream(LOG_EMPH) << "receive high-degree edges: " + << ti.current_time() + << " secs" + << std::endl; } +#endif } } @@ -442,7 +469,7 @@ namespace graphlab { } } -//#ifdef TUNING +#ifdef TUNING // Compute the total number of high-degree and low-degree vertices std::vector swap_counts(hybrid_rpc.numprocs()); @@ -485,7 +512,7 @@ namespace graphlab { << " secs" << std::endl; } -//#endif +#endif } From bdedcd24a03dde625543a91ba7f9106d4dff4e7c Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Fri, 22 Nov 2013 21:54:03 +0800 Subject: [PATCH 19/50] patch missing hdfs loading --- src/graphlab/graph/distributed_graph.hpp | 60 +++++++++++++++++------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/src/graphlab/graph/distributed_graph.hpp b/src/graphlab/graph/distributed_graph.hpp index 76a63c13ab..1d8627e698 100644 --- a/src/graphlab/graph/distributed_graph.hpp +++ b/src/graphlab/graph/distributed_graph.hpp @@ -2326,29 +2326,55 @@ namespace graphlab { if (graph_files.size() == 0) { logstream(LOG_WARNING) << "No files found matching " << prefix << std::endl; } + + if (get_cuts_type() == HYBRID_GINGER_CUTS) { + for(size_t i = 0; i < graph_files.size(); ++i) { + if ((parallel_ingress && (i % rpc.numprocs() == rpc.procid())) || + (!parallel_ingress && (rpc.procid() == 0))) { + logstream(LOG_EMPH) << "Loading graph from file: " << graph_files[i] << std::endl; + // is it a gzip file ? + const bool gzip = boost::ends_with(graph_files[i], ".gz"); + // open the stream + graphlab::hdfs::fstream in_file(hdfs, graph_files[i]); + boost::iostreams::filtering_stream fin; + if(gzip) fin.push(boost::iostreams::gzip_decompressor()); + fin.push(in_file); + const bool success = load_from_stream(graph_files[i], fin, line_parser); + if(!success) { + logstream(LOG_FATAL) + << "\n\tError parsing file: " << graph_files[i] << std::endl; + } + fin.pop(); + if (gzip) fin.pop(); + } + } + } + else { #ifdef _OPENMP #pragma omp parallel for #endif - for(size_t i = 0; i < graph_files.size(); ++i) { - if ((parallel_ingress && (i % rpc.numprocs() == rpc.procid())) || - (!parallel_ingress && (rpc.procid() == 0))) { - logstream(LOG_EMPH) << "Loading graph from file: " << graph_files[i] << std::endl; - // is it a gzip file ? - const bool gzip = boost::ends_with(graph_files[i], ".gz"); - // open the stream - graphlab::hdfs::fstream in_file(hdfs, graph_files[i]); - boost::iostreams::filtering_stream fin; - if(gzip) fin.push(boost::iostreams::gzip_decompressor()); - fin.push(in_file); - const bool success = load_from_stream(graph_files[i], fin, line_parser); - if(!success) { - logstream(LOG_FATAL) - << "\n\tError parsing file: " << graph_files[i] << std::endl; + for(size_t i = 0; i < graph_files.size(); ++i) { + if ((parallel_ingress && (i % rpc.numprocs() == rpc.procid())) || + (!parallel_ingress && (rpc.procid() == 0))) { + logstream(LOG_EMPH) << "Loading graph from file: " << graph_files[i] << std::endl; + // is it a gzip file ? + const bool gzip = boost::ends_with(graph_files[i], ".gz"); + // open the stream + graphlab::hdfs::fstream in_file(hdfs, graph_files[i]); + boost::iostreams::filtering_stream fin; + if(gzip) fin.push(boost::iostreams::gzip_decompressor()); + fin.push(in_file); + const bool success = load_from_stream(graph_files[i], fin, line_parser); + if(!success) { + logstream(LOG_FATAL) + << "\n\tError parsing file: " << graph_files[i] << std::endl; + } + fin.pop(); + if (gzip) fin.pop(); } - fin.pop(); - if (gzip) fin.pop(); } } + rpc.full_barrier(); } // end of load from hdfs From d19623734eb3024280099023840c9dcdacb22e13 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Sun, 24 Nov 2013 10:13:44 +0800 Subject: [PATCH 20/50] sweep supporting code for radj, meaningless --- src/graphlab/graph/builtin_parsers.hpp | 70 +--- src/graphlab/graph/distributed_graph.hpp | 30 +- .../ingress/distributed_hybrid_ingress.hpp | 328 +++++------------- .../ingress/distributed_ingress_base.hpp | 5 - 4 files changed, 89 insertions(+), 344 deletions(-) diff --git a/src/graphlab/graph/builtin_parsers.hpp b/src/graphlab/graph/builtin_parsers.hpp index 8661d9349e..2900a183ea 100644 --- a/src/graphlab/graph/builtin_parsers.hpp +++ b/src/graphlab/graph/builtin_parsers.hpp @@ -96,7 +96,7 @@ namespace graphlab { } // end of tsv parser /** - * \brief Parse files in the reverse tsv format + * \brief Parse files in the reverse tsv format (for debug) * * This is identical to the tsv format but reverse edge direction. * @@ -197,74 +197,6 @@ namespace graphlab { } // end of adj parser #endif -#if defined(__cplusplus) && __cplusplus >= 201103L - // The spirit parser seems to have issues when compiling under - // C++11. Temporary workaround with a hard coded parser. TOFIX - template - bool radj_parser(Graph& graph, const std::string& srcfilename, - const std::string& line) { - // If the line is empty simply skip it - if(line.empty()) return true; - std::stringstream strm(line); - vertex_id_type target; - size_t n; - strm >> target; - if (strm.fail()) return false; - strm >> n; - if (strm.fail()) return true; - - std::vector sources; - while (strm.good()) { - vertex_id_type source; - strm >> source; - if (strm.fail()) break; - if (target != source) sources.push_back(source); - } - std::vector edatas; - edatas.resize(sources.size()); - graph.add_edge(sources, target, edatas); - if (n != sources.size()) return false; - return true; - } // end of radj parser - -#else - - template - bool radj_parser(Graph& graph, const std::string& srcfilename, - const std::string& line) { - // If the line is empty simply skip it - if(line.empty()) return true; - // We use the boost spirit parser which requires (too) many separate - // namespaces so to make things clear we shorten them here. - namespace qi = boost::spirit::qi; - namespace ascii = boost::spirit::ascii; - namespace phoenix = boost::phoenix; - vertex_id_type target(-1); - vertex_id_type nsources(-1); - std::vector sources; - const bool success = qi::phrase_parse - (line.begin(), line.end(), - // Begin grammar - ( - qi::ulong_[phoenix::ref(target) = qi::_1] >> -qi::char_(",") >> - qi::ulong_[phoenix::ref(nsources) = qi::_1] >> -qi::char_(",") >> - *(qi::ulong_[phoenix::push_back(phoenix::ref(sources), qi::_1)] % -qi::char_(",")) - ) - , - // End grammar - ascii::space); - // Test to see if the boost parser was able to parse the line - if(!success || nsources != sources.size()) { - logstream(LOG_ERROR) << "Parse error in vertex prior parser." << std::endl; - return false; - } - std::vector edatas; - edatas.resize(sources.size()); - graph.add_edge(sources, target, edatas); - return true; - } // end of radj parser -#endif - template struct tsv_writer{ typedef typename Graph::vertex_type vertex_type; diff --git a/src/graphlab/graph/distributed_graph.hpp b/src/graphlab/graph/distributed_graph.hpp index 1d8627e698..d80b16ac7d 100644 --- a/src/graphlab/graph/distributed_graph.hpp +++ b/src/graphlab/graph/distributed_graph.hpp @@ -932,31 +932,6 @@ namespace graphlab { return true; } - /* used by radj */ - void add_edge(std::vector& sources, vertex_id_type target, - const std::vector& edatas) { -#ifndef USE_DYNAMIC_LOCAL_GRAPH - if(finalized) { - logstream(LOG_FATAL) - << "\n\tAttempting to add an edge to a finalized graph." - << "\n\tEdges cannot be added to a graph after finalization." - << std::endl; - } -#else - finalized = false; -#endif - if(target == vertex_id_type(-1)) { - logstream(LOG_FATAL) - << "\n\tThe target vertex with id vertex_id_type(-1)\n" - << "\tor unsigned value " << vertex_id_type(-1) << " in edge \n" - << "\tThe -1 vertex id is reserved for internal use." - << std::endl; - } - ASSERT_NE(ingress_ptr, NULL); - - ingress_ptr->add_edges(sources, target, edatas); - } - /** * \brief Performs a map-reduce operation on each vertex in the * graph returning the result. @@ -2535,13 +2510,10 @@ namespace graphlab { } else if (format == "adj") { line_parser = builtin_parsers::adj_parser; load(path, line_parser); - } else if (format == "radj") { - line_parser = builtin_parsers::radj_parser; - load(path, line_parser); } else if (format == "tsv") { line_parser = builtin_parsers::tsv_parser; load(path, line_parser); - } else if (format == "rtsv") { + } else if (format == "rtsv") { // debug line_parser = builtin_parsers::rtsv_parser; load(path, line_parser); } else if (format == "csv") { diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp index 9d741f0535..fca32c7c7b 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp @@ -94,28 +94,6 @@ namespace graphlab { buffered_exchange hybrid_edge_exchange; buffered_exchange hybrid_vertex_exchange; - /// batch ingress. e.g., R-ADJ - struct batch_edge_buffer_record { - std::vector sources; - vertex_id_type target; - std::vector edatas; - - batch_edge_buffer_record( - const std::vector& sources = std::vector() , - const vertex_id_type& target = vertex_id_type(-1), - const std::vector& edatas = std::vector()) : - sources(sources), target(target), edatas(edatas) { } - - void load(iarchive& arc) { arc >> sources >> target >> edatas; } - void save(oarchive& arc) const { arc << sources << target << edatas; } - }; - typedef typename buffered_exchange::buffer_type - batch_edge_buffer_type; - - buffered_exchange high_batch_edge_exchange; - buffered_exchange low_batch_edge_exchange; - - /// detail vertex record for the second pass coordination. typedef typename base_type::vertex_negotiator_record vertex_negotiator_record; @@ -128,13 +106,9 @@ namespace graphlab { #ifdef _OPENMP hybrid_edge_exchange(dc, omp_get_max_threads()), hybrid_vertex_exchange(dc, omp_get_max_threads()), - high_batch_edge_exchange(dc, omp_get_max_threads()), - low_batch_edge_exchange(dc, omp_get_max_threads()) #else hybrid_edge_exchange(dc), hybrid_vertex_exchange(dc), - high_batch_edge_exchange(dc), - low_batch_edge_exchange(dc) #endif { /* fast pass for standalone case. */ @@ -160,56 +134,6 @@ namespace graphlab { #endif } // end of add edge - - /** Add edges to the ingress object using different assignment policies. - * This function handles the RADJ graph in different ways. - * For high degree edges, hashing from its source vertex; - * for low degree edges, hasing from its target vertex. - */ - void add_edges(std::vector& sources, vertex_id_type target, - const std::vector& edatas) { - if (standalone) { - /* fast pass for standalone case. */ - const batch_edge_buffer_record record(sources, target, edatas); -#ifdef _OPENMP - low_batch_edge_exchange.send(0, record, omp_get_thread_num()); -#else - low_batch_edge_exchange.send(0, record); -#endif - } else { - const procid_t target_owner_proc = - graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); - - if(sources.size() > threshold){ - std::vector batch_rec_vector(hybrid_rpc.numprocs()); - - for (size_t i = 0; i < sources.size(); i++){ - const procid_t source_owner_proc = - graph_hash::hash_vertex(sources[i]) % hybrid_rpc.numprocs(); - batch_rec_vector[source_owner_proc].sources.push_back(sources[i]); - batch_rec_vector[source_owner_proc].edatas.push_back(edatas[i]); - } - - for (size_t i = 0; i < batch_rec_vector.size(); i++) { - if(batch_rec_vector[i].sources.size() > 0){ - batch_rec_vector[i].target=target; -#ifdef _OPENMP - high_batch_edge_exchange.send((procid_t)i, batch_rec_vector[i], omp_get_thread_num()); -#else - high_batch_edge_exchange.send((procid_t)i, batch_rec_vector[i]); -#endif - } - } - } else{ - const batch_edge_buffer_record record(sources, target, edatas); -#ifdef _OPENMP - low_batch_edge_exchange.send(target_owner_proc, record, omp_get_thread_num()); -#else - low_batch_edge_exchange.send(target_owner_proc, record); -#endif - } - } - } // end of add edges /* add vdata */ void add_vertex(vertex_id_type vid, const VertexData& vdata) { @@ -232,7 +156,7 @@ namespace graphlab { size_t nprocs = hybrid_rpc.numprocs(); procid_t l_procid = hybrid_rpc.procid(); size_t nedges = 0; - + if (l_procid == 0) { memory_info::log_usage("start finalizing"); logstream(LOG_EMPH) << "hybrid finalizing ..." @@ -242,159 +166,108 @@ namespace graphlab { << std::endl; } + + /**************************************************************************/ + /* */ + /* Flush any additional data */ + /* */ + /**************************************************************************/ + hybrid_edge_exchange.flush(); hybrid_vertex_exchange.flush(); + + /** + * Fast pass for redundant finalization with no graph changes. + */ + { + size_t changed_size = hybrid_edge_exchange.size() + hybrid_vertex_exchange.size(); + hybrid_rpc.all_reduce(changed_size); + if (changed_size == 0) { + logstream(LOG_INFO) << "Skipping Graph Finalization because no changes happened..." << std::endl; + return; + } + } + + /**************************************************************************/ /* */ /* prepare hybrid ingress */ /* */ /**************************************************************************/ { - if (standalone) { - /* fast pass for redundant finalization with no graph changes. */ - hybrid_edge_exchange.flush(); - if (hybrid_edge_exchange.size() > 0) { - edge_buffer_type edge_buffer; - procid_t proc = -1; - - nedges = hybrid_edge_exchange.size(); - hybrid_edges.reserve(nedges); - - while(hybrid_edge_exchange.recv(proc, edge_buffer)) - foreach(const edge_buffer_record& rec, edge_buffer) - hybrid_edges.push_back(rec); - - hybrid_edge_exchange.clear(); - } + edge_buffer_type edge_buffer; + procid_t proc; + nedges = hybrid_edge_exchange.size(); + + hybrid_edges.reserve(nedges); + if (standalone) { /* fast pass for standalone */ + proc = -1; + while(hybrid_edge_exchange.recv(proc, edge_buffer)) + foreach(const edge_buffer_record& rec, edge_buffer) + hybrid_edges.push_back(rec); + + hybrid_edge_exchange.clear(); } else { hopscotch_map in_degree_set; - edge_buffer_type edge_buffer; - procid_t proc; - - /* collect edges for one-by-one ingress (e.g. SNAP) */ - hybrid_edge_exchange.flush(); - if (hybrid_edge_exchange.size() > 0) { - nedges = hybrid_edge_exchange.size(); - hybrid_edges.reserve(nedges); - - proc = -1; - while(hybrid_edge_exchange.recv(proc, edge_buffer)) { - foreach(const edge_buffer_record& rec, edge_buffer) { - hybrid_edges.push_back(rec); - in_degree_set[rec.target]++; - } - } - hybrid_edge_exchange.clear(); - hybrid_rpc.full_barrier(); // sync before reusing -#ifdef TUNING - if(l_procid == 0) { - memory_info::log_usage("save local edges and count in-degree done."); - logstream(LOG_EMPH) << "save local edges and count in-degree: " - << ti.current_time() - << " secs" - << std::endl; - } -#endif - - // re-send edges of high-degree vertices - for (size_t i = 0; i < hybrid_edges.size(); i++) { - edge_buffer_record& rec = hybrid_edges[i]; - if (in_degree_set[rec.target] > threshold) { - const procid_t source_owner_proc = - graph_hash::hash_vertex(rec.source) % nprocs; - if(source_owner_proc != l_procid){ - // re-send the edge of high-degree vertices according to source - hybrid_edge_exchange.send(source_owner_proc, rec); - // set re-sent edges as empty for skipping - hybrid_edges[i] = edge_buffer_record(); - --nedges; - } - } - } -#ifdef TUNING - if(l_procid == 0) { - memory_info::log_usage("resend edges of high-degree vertices done."); - logstream(LOG_EMPH) << "resend edges of high-degree vertices: " - << ti.current_time() - << " secs" - << std::endl; - } -#endif - // receive edges of high-degree vertices - hybrid_edge_exchange.flush(); -#ifdef TUNING - if(l_procid == 0) - logstream(LOG_INFO) << "receive high-degree edges: " - << hybrid_edge_exchange.size() << std::endl; -#endif - proc = -1; - while(hybrid_edge_exchange.recv(proc, edge_buffer)) { - foreach(const edge_buffer_record& rec, edge_buffer) { - hybrid_edges.push_back(rec); - ++nedges; - } + proc = -1; + while(hybrid_edge_exchange.recv(proc, edge_buffer)) { + foreach(const edge_buffer_record& rec, edge_buffer) { + hybrid_edges.push_back(rec); + in_degree_set[rec.target]++; } - hybrid_edge_exchange.clear(); - in_degree_set.clear(); + } + hybrid_edge_exchange.clear(); + hybrid_rpc.full_barrier(); // sync before reusing #ifdef TUNING - if(l_procid == 0) { - memory_info::log_usage("receive high-degree edges done."); - logstream(LOG_EMPH) << "receive high-degree edges: " - << ti.current_time() - << " secs" - << std::endl; - } -#endif + if(l_procid == 0) { + memory_info::log_usage("save local edges and count in-degree done."); + logstream(LOG_EMPH) << "save local edges and count in-degree: " + << ti.current_time() + << " secs" + << std::endl; } +#endif - } - - /* collect edges for batch ingress (e.g. RADJ) */ - // store edges of low-degree vertices into hybrid_edges - low_batch_edge_exchange.flush(); - if (low_batch_edge_exchange.size() > 0) { - batch_edge_buffer_type batch_edge_buffer; - procid_t proc = -1; - while(low_batch_edge_exchange.recv(proc, batch_edge_buffer)) { - foreach(const batch_edge_buffer_record& batch_rec, batch_edge_buffer) { - nedges += batch_rec.sources.size(); - for(size_t i = 0; i < batch_rec.sources.size();i++){ - edge_buffer_record rec(batch_rec.sources[i], - batch_rec.target, - batch_rec.edatas[i]); - hybrid_edges.push_back(rec); + // re-send edges of high-degree vertices + for (size_t i = 0; i < hybrid_edges.size(); i++) { + edge_buffer_record& rec = hybrid_edges[i]; + if (in_degree_set[rec.target] > threshold) { + const procid_t source_owner_proc = + graph_hash::hash_vertex(rec.source) % nprocs; + if(source_owner_proc != l_procid){ + // re-send the edge of high-degree vertices according to source + hybrid_edge_exchange.send(source_owner_proc, rec); + // set re-sent edges as empty for skipping + hybrid_edges[i] = edge_buffer_record(); + --nedges; } } } - low_batch_edge_exchange.clear(); #ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("receive low-degree edges done."); - logstream(LOG_EMPH) << "receive low-degree edges: " + memory_info::log_usage("resend edges of high-degree vertices done."); + logstream(LOG_EMPH) << "resend edges of high-degree vertices: " << ti.current_time() << " secs" << std::endl; } #endif - } - - // store edges of high-degree vertices into hybrid_edges - high_batch_edge_exchange.flush(); - if (high_batch_edge_exchange.size() > 0) { - batch_edge_buffer_type batch_edge_buffer; - procid_t proc = -1; - while(high_batch_edge_exchange.recv(proc, batch_edge_buffer)) { - foreach(const batch_edge_buffer_record& batch_rec, batch_edge_buffer) { - nedges += batch_rec.sources.size(); - for(size_t i = 0; i < batch_rec.sources.size(); i++){ - edge_buffer_record rec(batch_rec.sources[i], - batch_rec.target, - batch_rec.edatas[i]); - hybrid_edges.push_back(rec); - } + // receive edges of high-degree vertices + hybrid_edge_exchange.flush(); +#ifdef TUNING + if(l_procid == 0) + logstream(LOG_INFO) << "receive high-degree edges: " + << hybrid_edge_exchange.size() << std::endl; +#endif + proc = -1; + while(hybrid_edge_exchange.recv(proc, edge_buffer)) { + foreach(const edge_buffer_record& rec, edge_buffer) { + hybrid_edges.push_back(rec); + ++nedges; } } - high_batch_edge_exchange.clear(); + hybrid_edge_exchange.clear(); + in_degree_set.clear(); #ifdef TUNING if(l_procid == 0) { memory_info::log_usage("receive high-degree edges done."); @@ -417,22 +290,9 @@ namespace graphlab { // connect to base finalize() modified_base_finalize(nedges); - if(l_procid == 0) { - memory_info::log_usage("base finalizing done."); - logstream(LOG_EMPH) << "base finalizing. (" - << ti.current_time() - << " secs)" - << std::endl; - } + // set vertex type for hybrid engine set_vertex_type(); - if(l_procid == 0) { - memory_info::log_usage("set vertex type done."); - logstream(LOG_EMPH) << "set vertex type. (" - << ti.current_time() - << " secs)" - << std::endl; - } if(l_procid == 0) { memory_info::log_usage("hybrid finalizing graph done."); @@ -563,33 +423,12 @@ namespace graphlab { dense_bitset updated_lvids(graph.vid2lvid.size()); - /**************************************************************************/ - /* */ - /* Flush any additional data */ - /* */ - /**************************************************************************/ - hybrid_vertex_exchange.flush(); /* edges has stored in hybrid_edges */ - - /** - * Fast pass for redundant finalization with no graph changes. - */ - { - size_t changed_size = nedges + hybrid_vertex_exchange.size(); - hybrid_rpc.all_reduce(changed_size); - if (changed_size == 0) { - logstream(LOG_INFO) << "Skipping Graph Finalization because no changes happened..." << std::endl; - return; - } - } - - /**************************************************************************/ /* */ /* Construct local graph */ /* */ /**************************************************************************/ - { - // Add all the edges to the local graph + { // Add all the edges to the local graph graph.local_graph.reserve_edge_space(nedges + 1); foreach(const edge_buffer_record& rec, hybrid_edges) { @@ -894,7 +733,6 @@ namespace graphlab { } base_type::exchange_global_info(standalone); - #ifdef TUNING if(l_procid == 0) { memory_info::log_usage("exchange global info done."); @@ -904,6 +742,14 @@ namespace graphlab { << std::endl; } #endif + + if(l_procid == 0) { + memory_info::log_usage("base finalizing done."); + logstream(LOG_EMPH) << "base finalizing. (" + << ti.current_time() + << " secs)" + << std::endl; + } } // end of modified base finalize private: diff --git a/src/graphlab/graph/ingress/distributed_ingress_base.hpp b/src/graphlab/graph/ingress/distributed_ingress_base.hpp index a23794e00f..c5907d1417 100644 --- a/src/graphlab/graph/ingress/distributed_ingress_base.hpp +++ b/src/graphlab/graph/ingress/distributed_ingress_base.hpp @@ -129,11 +129,6 @@ namespace graphlab { edge_exchange.send(owning_proc, record); } // end of add edge - /** Stub for ginger ingress. */ - virtual void add_edges( - std::vector& in, - vertex_id_type target, - const std::vector& edatas) { } /** \brief Add an vertex to the ingress object. */ virtual void add_vertex(vertex_id_type vid, const VertexData& vdata) { From 69b150846acab3cb3dfcdb152e70686c7939c8a0 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Sun, 24 Nov 2013 19:46:07 +0800 Subject: [PATCH 21/50] refine ginger --- src/graphlab/graph/distributed_graph.hpp | 128 ++-- .../distributed_hybrid_ginger_ingress.hpp | 575 ++++++++++-------- .../ingress/distributed_hybrid_ingress.hpp | 75 +-- .../graph/ingress/ingress_edge_decision.hpp | 34 +- 4 files changed, 396 insertions(+), 416 deletions(-) diff --git a/src/graphlab/graph/distributed_graph.hpp b/src/graphlab/graph/distributed_graph.hpp index d80b16ac7d..cea1c66595 100644 --- a/src/graphlab/graph/distributed_graph.hpp +++ b/src/graphlab/graph/distributed_graph.hpp @@ -2219,61 +2219,32 @@ namespace graphlab { logstream(LOG_WARNING) << "No files found matching " << original_path << std::endl; } - if (get_cuts_type() == HYBRID_GINGER_CUTS) { - for(size_t i = 0; i < graph_files.size(); ++i) { - if ((parallel_ingress && (i % rpc.numprocs() == rpc.procid())) - || (!parallel_ingress && (rpc.procid() == 0))) { - logstream(LOG_EMPH) << "Loading graph from file (sequential): " - << graph_files[i] << std::endl; - // is it a gzip file ? - const bool gzip = boost::ends_with(graph_files[i], ".gz"); - // open the stream - std::ifstream in_file(graph_files[i].c_str(), - std::ios_base::in | std::ios_base::binary); - // attach gzip if the file is gzip - boost::iostreams::filtering_stream fin; - // Using gzip filter - if (gzip) fin.push(boost::iostreams::gzip_decompressor()); - fin.push(in_file); - const bool success = load_from_stream(graph_files[i], fin, line_parser); - if(!success) { - logstream(LOG_FATAL) - << "\n\tError parsing file: " << graph_files[i] << std::endl; - } - fin.pop(); - if (gzip) fin.pop(); - } - } - } - else { #ifdef _OPENMP #pragma omp parallel for #endif - for(size_t i = 0; i < graph_files.size(); ++i) { - if ((parallel_ingress && (i % rpc.numprocs() == rpc.procid())) - || (!parallel_ingress && (rpc.procid() == 0))) { - logstream(LOG_EMPH) << "Loading graph from file: " << graph_files[i] << std::endl; - // is it a gzip file ? - const bool gzip = boost::ends_with(graph_files[i], ".gz"); - // open the stream - std::ifstream in_file(graph_files[i].c_str(), - std::ios_base::in | std::ios_base::binary); - // attach gzip if the file is gzip - boost::iostreams::filtering_stream fin; - // Using gzip filter - if (gzip) fin.push(boost::iostreams::gzip_decompressor()); - fin.push(in_file); - const bool success = load_from_stream(graph_files[i], fin, line_parser); - if(!success) { - logstream(LOG_FATAL) - << "\n\tError parsing file: " << graph_files[i] << std::endl; - } - fin.pop(); - if (gzip) fin.pop(); + for(size_t i = 0; i < graph_files.size(); ++i) { + if ((parallel_ingress && (i % rpc.numprocs() == rpc.procid())) + || (!parallel_ingress && (rpc.procid() == 0))) { + logstream(LOG_EMPH) << "Loading graph from file: " << graph_files[i] << std::endl; + // is it a gzip file ? + const bool gzip = boost::ends_with(graph_files[i], ".gz"); + // open the stream + std::ifstream in_file(graph_files[i].c_str(), + std::ios_base::in | std::ios_base::binary); + // attach gzip if the file is gzip + boost::iostreams::filtering_stream fin; + // Using gzip filter + if (gzip) fin.push(boost::iostreams::gzip_decompressor()); + fin.push(in_file); + const bool success = load_from_stream(graph_files[i], fin, line_parser); + if(!success) { + logstream(LOG_FATAL) + << "\n\tError parsing file: " << graph_files[i] << std::endl; } + fin.pop(); + if (gzip) fin.pop(); } } - rpc.full_barrier(); } // end of load from posixfs @@ -2302,54 +2273,29 @@ namespace graphlab { logstream(LOG_WARNING) << "No files found matching " << prefix << std::endl; } - if (get_cuts_type() == HYBRID_GINGER_CUTS) { - for(size_t i = 0; i < graph_files.size(); ++i) { - if ((parallel_ingress && (i % rpc.numprocs() == rpc.procid())) || - (!parallel_ingress && (rpc.procid() == 0))) { - logstream(LOG_EMPH) << "Loading graph from file: " << graph_files[i] << std::endl; - // is it a gzip file ? - const bool gzip = boost::ends_with(graph_files[i], ".gz"); - // open the stream - graphlab::hdfs::fstream in_file(hdfs, graph_files[i]); - boost::iostreams::filtering_stream fin; - if(gzip) fin.push(boost::iostreams::gzip_decompressor()); - fin.push(in_file); - const bool success = load_from_stream(graph_files[i], fin, line_parser); - if(!success) { - logstream(LOG_FATAL) - << "\n\tError parsing file: " << graph_files[i] << std::endl; - } - fin.pop(); - if (gzip) fin.pop(); - } - } - } - else { #ifdef _OPENMP #pragma omp parallel for #endif - for(size_t i = 0; i < graph_files.size(); ++i) { - if ((parallel_ingress && (i % rpc.numprocs() == rpc.procid())) || - (!parallel_ingress && (rpc.procid() == 0))) { - logstream(LOG_EMPH) << "Loading graph from file: " << graph_files[i] << std::endl; - // is it a gzip file ? - const bool gzip = boost::ends_with(graph_files[i], ".gz"); - // open the stream - graphlab::hdfs::fstream in_file(hdfs, graph_files[i]); - boost::iostreams::filtering_stream fin; - if(gzip) fin.push(boost::iostreams::gzip_decompressor()); - fin.push(in_file); - const bool success = load_from_stream(graph_files[i], fin, line_parser); - if(!success) { - logstream(LOG_FATAL) - << "\n\tError parsing file: " << graph_files[i] << std::endl; - } - fin.pop(); - if (gzip) fin.pop(); + for(size_t i = 0; i < graph_files.size(); ++i) { + if ((parallel_ingress && (i % rpc.numprocs() == rpc.procid())) || + (!parallel_ingress && (rpc.procid() == 0))) { + logstream(LOG_EMPH) << "Loading graph from file: " << graph_files[i] << std::endl; + // is it a gzip file ? + const bool gzip = boost::ends_with(graph_files[i], ".gz"); + // open the stream + graphlab::hdfs::fstream in_file(hdfs, graph_files[i]); + boost::iostreams::filtering_stream fin; + if(gzip) fin.push(boost::iostreams::gzip_decompressor()); + fin.push(in_file); + const bool success = load_from_stream(graph_files[i], fin, line_parser); + if(!success) { + logstream(LOG_FATAL) + << "\n\tError parsing file: " << graph_files[i] << std::endl; } + fin.pop(); + if (gzip) fin.pop(); } } - rpc.full_barrier(); } // end of load from hdfs diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp index d0b600eb3e..a97c22238c 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp @@ -42,7 +42,7 @@ #include -#undef TUNING +#define TUNING namespace graphlab { template class distributed_graph; @@ -117,22 +117,6 @@ namespace graphlab { typedef typename buffered_exchange::buffer_type proc_edges_buffer_type; - /* ingress edges */ - buffered_exchange single_edge_exchange; - buffered_exchange high_edge_exchange; - buffered_exchange low_edge_exchange; - /* ingress vertex data */ - buffered_exchange hybrid_vertex_exchange; - buffered_exchange temporary_vertex_exchange; - - /** master hash table: - * mht is the synchronized one across the cluster, - * mht_incremental is the new added mht which stays local since the last sync. - */ - master_hash_table_type mht; - master_hash_table_type mht_incremental; - - buffered_exchange master_exchange; /// The rpc interface for this object dc_dist_object hybrid_rpc; @@ -146,16 +130,33 @@ namespace graphlab { std::vector hybrid_edges; + /* ingress exchange */ + buffered_exchange hybrid_edge_exchange; + buffered_exchange hybrid_vertex_exchange; + + buffered_exchange high_edge_exchange; + buffered_exchange low_edge_exchange; + buffered_exchange resend_vertex_exchange; + + /** master hash table: + * mht is the synchronized one across the cluster, + * mht_incremental is the new added mht which stays local since the last sync. + */ + // consider both #edge and #vertex + master_hash_table_type mht; + buffered_exchange mht_exchange; + master_hash_table_type mht_incr; + //these are used for edges balance + std::vector proc_balance; buffered_exchange< proc_edges_pair_type > proc_edges_exchange; - std::vector proc_edges_incremental; - std::vector proc_num_vertices; + std::vector proc_edges_incr; /// heuristic model from fennel /// records about the number of edges and vertices in the graph /// given from the commandline - size_t nedges; - size_t nverts; + size_t tot_nedges; + size_t tot_nverts; /// threshold for incremental mht to be synced across the cluster /// when the incremental mht size reaches the preset interval, /// we will perform a synchronization on mht across the cluster @@ -167,19 +168,25 @@ namespace graphlab { public: distributed_hybrid_ginger_ingress(distributed_control& dc, graph_type& graph, - size_t threshold = 100, size_t nedges = 0, size_t nverts = 0, + size_t threshold = 100, size_t tot_nedges = 0, size_t tot_nverts = 0, size_t interval = std::numeric_limits::max()) : - base_type(dc, graph), - single_edge_exchange(dc), high_edge_exchange(dc), low_edge_exchange(dc), - hybrid_vertex_exchange(dc), temporary_vertex_exchange(dc), master_exchange(dc), - hybrid_rpc(dc, this), graph(graph), threshold(threshold), - proc_edges_exchange(dc), proc_edges_incremental(dc.numprocs()), - proc_num_vertices(dc.numprocs()), - nedges(nedges), nverts(nverts), interval(interval) { - ASSERT_GT(nedges, 0); ASSERT_GT(nverts, 0); + base_type(dc, graph), hybrid_rpc(dc, this), + graph(graph), threshold(threshold), +#ifdef _OPENMP + hybrid_edge_exchange(dc, omp_get_max_threads()), + hybrid_vertex_exchange(dc, omp_get_max_threads()), +#else + hybrid_edge_exchange(dc), + hybrid_vertex_exchange(dc), +#endif + high_edge_exchange(dc), low_edge_exchange(dc), resend_vertex_exchange(dc), + mht_exchange(dc), proc_balance(dc.numprocs()), proc_edges_exchange(dc), + proc_edges_incr(dc.numprocs()), + tot_nedges(tot_nedges), tot_nverts(tot_nverts), interval(interval) { + ASSERT_GT(tot_nedges, 0); ASSERT_GT(tot_nverts, 0); gamma = 1.5; - alpha = sqrt(dc.numprocs()) * nedges / pow(nverts, gamma); + alpha = sqrt(dc.numprocs()) * tot_nedges / pow(tot_nverts, gamma); /* fast pass for standalone case. */ standalone = hybrid_rpc.numprocs() == 1; @@ -195,149 +202,150 @@ namespace graphlab { void add_edge(vertex_id_type source, vertex_id_type target, const EdgeData& edata) { const edge_buffer_record record(source, target, edata); - if (standalone) { - /* Fast pass for standalone case. */ - // NOTE: forbidden loading in parallel - hybrid_edges.push_back(record); - } else { - const procid_t owning_proc = - graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); - single_edge_exchange.send(owning_proc, record); - } + const procid_t owning_proc = standalone ? 0 : + graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); +#ifdef _OPENMP + hybrid_edge_exchange.send(owning_proc, record, omp_get_thread_num()); +#else + hybrid_edge_exchange.send(owning_proc, record); +#endif } // end of add edge - /** Add edges to the ingress object using different assignment policies. - * This function handles the RADJ graph in different ways. - * For high degree edges, hashing from its source vertex; - * for low degree edges, using a heuristic way named ginger. - */ - void add_edges(std::vector& in, vertex_id_type out, - const std::vector& edatas) { - if (standalone) { - /* fast pass for standalone case. */ - for(size_t i = 0; i < in.size();i++){ - const edge_buffer_record record(in[i], out, edatas[i]); - hybrid_edges.push_back(record); - } - } else { - procid_t owning_proc = 0; - if (in.size() > threshold) { - // TODO: no need send, just resend latter - owning_proc = graph_hash::hash_vertex(out) % base_type::rpc.numprocs(); - for (size_t i = 0; i < in.size(); ++i) { - edge_buffer_record record(in[i], out, edatas[i]); - high_edge_exchange.send(owning_proc, record); - } - } else { - owning_proc = base_type::edge_decision.edge_to_proc_ginger( - out, mht, mht_incremental, proc_num_vertices, - alpha, gamma, in); - for (size_t i = 0; i < in.size(); ++i) { - edge_buffer_record record(in[i], out, edatas[i]); - low_edge_exchange.send(owning_proc, record); - } - - // update local counter - proc_edges_incremental[owning_proc] += in.size(); + + /* add vdata */ + void add_vertex(vertex_id_type vid, const VertexData& vdata) { + const vertex_buffer_record record(vid, vdata); + const procid_t owning_proc = standalone ? 0 : + graph_hash::hash_vertex(vid) % hybrid_rpc.numprocs(); +#ifdef _OPENMP + hybrid_vertex_exchange.send(owning_proc, record, omp_get_thread_num()); +#else + hybrid_vertex_exchange.send(owning_proc, record); +#endif + } // end of add vertex + + bool is_sync() { return mht_incr.size() > interval; } + + void sync_heurisitc_info() { + size_t nprocs = hybrid_rpc.numprocs(); + procid_t l_procid = hybrid_rpc.procid(); + + /* send mht_incr */ + for (typename master_hash_table_type::iterator it = mht_incr.begin(); + it != mht_incr.end(); ++it) { + // broadcast + for (procid_t i = 0; i < nprocs; ++i) { + if (i != l_procid) + mht_exchange.send(i, master_pair_type(it->first, it->second)); } + // update mht + mht[it->first] = it->second; + } + mht_incr.clear(); + mht_exchange.partial_flush(0); + + /* try to receive mht_incr and update mht */ + master_buffer_type master_buffer; + procid_t proc = -1; + while(mht_exchange.recv(proc, master_buffer, false)) { + foreach(const master_pair_type& pair, master_buffer) + mht[pair.first] = pair.second; + } + mht_exchange.clear(); - // update mapping table for ginger - size_t numprocs = base_type::rpc.numprocs(); - mht_incremental[out] = owning_proc; - - // broadcast local counters - if (mht_incremental.size() > interval) { - //update mht - for (typename master_hash_table_type::iterator it = mht_incremental.begin(); - it != mht_incremental.end(); ++it) { - for (procid_t i = 0; i < numprocs; ++i) { - if (i != hybrid_rpc.procid()) - master_exchange.send(i, master_pair_type(it->first, it->second)); - } - mht[it->first] = it->second; - } - master_exchange.partial_flush(0); - mht_incremental.clear(); - master_buffer_type master_buffer; - procid_t proc; - while(master_exchange.recv(proc, master_buffer)) { - foreach(const master_pair_type& pair, master_buffer) { - mht[pair.first] = pair.second; - //proc_num_vertices[pair.second]++; - } - } - - //update proc_edges for edges balance - for (procid_t p=0; p& sources, vertex_id_type target, + const std::vector& edatas) { + size_t nprocs = hybrid_rpc.numprocs(); + procid_t owning_proc = 0; + + if (sources.size() > threshold) { + // TODO: no need send, just resend latter + owning_proc = graph_hash::hash_vertex(target) % nprocs; + for (size_t i = 0; i < sources.size(); ++i) { + edge_buffer_record record(sources[i], target, edatas[i]); + high_edge_exchange.send(owning_proc, record); + } } else { - const procid_t owning_proc = - graph_hash::hash_vertex(vid) % hybrid_rpc.numprocs(); - temporary_vertex_exchange.send(owning_proc, record); + owning_proc = base_type::edge_decision.edge_to_proc_ginger( + sources, target, mht, mht_incr, proc_balance, alpha, gamma); + for (size_t i = 0; i < sources.size(); ++i) { + edge_buffer_record record(sources[i], target, edatas[i]); + low_edge_exchange.send(owning_proc, record); + } + + // update edge counter + proc_edges_incr[owning_proc] += sources.size(); } - } // end of add vertex + // update vertex mapping table + mht_incr[target] = owning_proc; + // synchronize heurisitic info + if (is_sync()) sync_heurisitc_info(); + } // end of add edges - void assign_edges() { - single_edge_exchange.flush(); - if (single_edge_exchange.size() > 0) { - typedef typename boost::unordered_map - batch_record_map_type; - batch_record_map_type batch_map; - edge_buffer_type edge_buffer; - procid_t proc = -1; - while (single_edge_exchange.recv(proc, edge_buffer)) { - foreach(const edge_buffer_record& rec, edge_buffer) { - batch_map[rec.target].sources.push_back(rec.source); - batch_map[rec.target].edatas.push_back(rec.edata); - } - } - // re-assigne - for (typename batch_record_map_type::iterator it = batch_map.begin(); - it != batch_map.end(); ++it) { - add_edges(it->second.sources, it->first,it->second.edatas); + void assign_hybrid_edges() { + typedef typename boost::unordered_map + batch_record_map_type; + batch_record_map_type batch_map; + + edge_buffer_type edge_buffer; + procid_t proc = -1; + while (hybrid_edge_exchange.recv(proc, edge_buffer)) { + foreach(const edge_buffer_record& rec, edge_buffer) { + batch_map[rec.target].sources.push_back(rec.source); + batch_map[rec.target].edatas.push_back(rec.edata); } + } + hybrid_edge_exchange.clear(); + + if (hybrid_rpc.procid() == 0) + logstream(LOG_INFO) << "receive " << batch_map.size() + << " batch edges done." << std::endl; - single_edge_exchange.clear(); - //hybrid_rpc.full_barrier(); + for (typename batch_record_map_type::iterator it = batch_map.begin(); + it != batch_map.end(); ++it) { + add_edges(it->second.sources, it->first, it->second.edatas); } } void finalize() { graphlab::timer ti; - + size_t nprocs = hybrid_rpc.numprocs(); procid_t l_procid = hybrid_rpc.procid(); size_t nedges = 0; + hybrid_rpc.full_barrier(); + if (l_procid == 0) { memory_info::log_usage("start finalizing"); logstream(LOG_EMPH) << "ginger finalizing ..." @@ -349,77 +357,87 @@ namespace graphlab { /**************************************************************************/ /* */ - /* Assign edges for one-by-one format */ + /* Flush any additional data */ /* */ /**************************************************************************/ - if (!standalone) assign_edges(); + hybrid_edge_exchange.flush(); hybrid_vertex_exchange.flush(); /** * Fast pass for redundant finalization with no graph changes. */ - size_t changed_size; - - // flush and count changes - if (standalone) { - nedges = hybrid_edges.size(); - hybrid_vertex_exchange.flush(); - - changed_size = nedges + hybrid_vertex_exchange.size(); - } else { - high_edge_exchange.flush(); - low_edge_exchange.flush(); - temporary_vertex_exchange.flush(); - - changed_size = high_edge_exchange.size() - + low_edge_exchange.size() - + hybrid_vertex_exchange.size(); + { + size_t changed_size = hybrid_edge_exchange.size() + hybrid_vertex_exchange.size(); + hybrid_rpc.all_reduce(changed_size); + if (changed_size == 0) { + logstream(LOG_INFO) << "Skipping Graph Finalization because no changes happened..." << std::endl; + return; + } } + + + /**************************************************************************/ + /* */ + /* Assign edges */ + /* */ + /**************************************************************************/ + if (!standalone) assign_hybrid_edges(); - hybrid_rpc.all_reduce(changed_size); - if (changed_size == 0) { - logstream(LOG_INFO) << "Skipping Graph Finalization because no changes happened..." << std::endl; - return; +#ifdef TUNING + if(l_procid == 0) { + logstream(LOG_INFO) << "assign edges: " + << ti.current_time() + << " secs" + << std::endl; } +#endif /**************************************************************************/ /* */ - /* prepare hybrid ingress */ + /* Prepare hybrid ingress */ /* */ /**************************************************************************/ - if (!standalone) { - /* exchange mapping table*/ - for (typename master_hash_table_type::iterator it = mht_incremental.begin(); - it != mht_incremental.end(); ++it) { + if (standalone) { /* fast pass for standalone */ + edge_buffer_type edge_buffer; + procid_t proc = -1; + nedges = hybrid_edge_exchange.size(); + + while(hybrid_edge_exchange.recv(proc, edge_buffer)) { + foreach(const edge_buffer_record& rec, edge_buffer) + hybrid_edges.push_back(rec); + } + hybrid_edge_exchange.clear(); + } else { + /* send and receive last mht_incr */ + for (typename master_hash_table_type::iterator it = mht_incr.begin(); + it != mht_incr.end(); ++it) { for (procid_t i = 0; i < nprocs; ++i) { if (i != l_procid) - master_exchange.send(i, master_pair_type(it->first, it->second)); + mht_exchange.send(i, master_pair_type(it->first, it->second)); } mht[it->first] = it->second; } - mht_incremental.clear(); + mht_incr.clear(); - master_exchange.flush(); + mht_exchange.flush(); master_buffer_type master_buffer; procid_t proc = -1; - while(master_exchange.recv(proc, master_buffer)) { - foreach(const master_pair_type& pair, master_buffer) { + while(mht_exchange.recv(proc, master_buffer)) { + foreach(const master_pair_type& pair, master_buffer) mht[pair.first] = pair.second; - } } - master_exchange.clear(); + mht_exchange.clear(); #ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("exchange mapping done."); - logstream(LOG_EMPH) << "exchange mapping: " + logstream(LOG_INFO) << "exchange mapping: " << ti.current_time() << " secs" << std::endl; } #endif + high_edge_exchange.flush(); low_edge_exchange.flush(); - /* collect edges for batch ingress (e.g. RADJ) */ nedges = low_edge_exchange.size(); hybrid_edges.reserve(nedges + high_edge_exchange.size()); @@ -429,41 +447,55 @@ namespace graphlab { foreach(const edge_buffer_record& rec, edge_buffer) { if (mht.find(rec.source) == mht.end()) mht[rec.source] = graph_hash::hash_vertex(rec.source) % nprocs; + hybrid_edges.push_back(rec); } } low_edge_exchange.clear(); - hybrid_rpc.full_barrier(); // sync before reusing - // re-send edges of high-degree vertices +#ifdef TUNING + if(l_procid == 0) { + logstream(LOG_INFO) << "low-degree edges: " + << ti.current_time() + << " secs" + << std::endl; + } +#endif + + + // re-send edges of high-degree vertices by hybrid_edge_exchange proc = -1; while(high_edge_exchange.recv(proc, edge_buffer)) { foreach(const edge_buffer_record& rec, edge_buffer) { if (mht.find(rec.source) == mht.end()) mht[rec.source] = graph_hash::hash_vertex(rec.source) % nprocs; - - const procid_t source_owner_proc = mht[rec.source]; - if (source_owner_proc == l_procid) { + + const procid_t owner_proc = mht[rec.source]; + if (owner_proc == l_procid) { hybrid_edges.push_back(rec); ++nedges; } else { - low_edge_exchange.send(source_owner_proc, rec); + hybrid_edge_exchange.send(owner_proc, rec); } } } high_edge_exchange.clear(); // receive edges of high-degree vertices - low_edge_exchange.flush(); + hybrid_edge_exchange.flush(); +#ifdef TUNING + logstream(LOG_INFO) << "receive #edges=" << hybrid_edge_exchange.size() + << std::endl; +#endif proc = -1; - while(low_edge_exchange.recv(proc, edge_buffer)) { + while(hybrid_edge_exchange.recv(proc, edge_buffer)) { foreach(const edge_buffer_record& rec, edge_buffer) { mht[rec.source] = l_procid; hybrid_edges.push_back(rec); ++nedges; } } - low_edge_exchange.clear(); + hybrid_edge_exchange.clear(); } if(l_procid == 0) { @@ -476,28 +508,16 @@ namespace graphlab { // connect to base finalize() modified_base_finalize(nedges); - if(l_procid == 0) { - memory_info::log_usage("base finalizing done."); - logstream(LOG_EMPH) << "base finalizing. (" - << ti.current_time() - << " secs)" - << std::endl; - } + // set vertex type for hybrid engine set_vertex_type(); + if(l_procid == 0) { - memory_info::log_usage("set vertex type done."); - logstream(LOG_EMPH) << "set vertex type. (" + logstream(LOG_EMPH) << "ginger finalizing graph. (" << ti.current_time() << " secs)" << std::endl; } - - if(l_procid == 0) - logstream(LOG_EMPH) << "ginger finalizing graph done. (" - << ti.current_time() - << " secs)" - << std::endl; } // end of finalize void set_vertex_type() { @@ -526,7 +546,7 @@ namespace graphlab { } } -//#ifdef TUNING +#ifdef TUNING // Compute the total number of high-degree and low-degree vertices std::vector swap_counts(hybrid_rpc.numprocs()); @@ -557,11 +577,11 @@ namespace graphlab { << (float(high_master)/(high_master+low_master)) << "]" << std::endl; if ((high_mirror + low_mirror) > 0) - logstream(LOG_EMPH) << "hybrid info: mirror [" - << high_mirror << " " - << low_mirror << " " - << (float(high_mirror)/(high_mirror+low_mirror)) << "]" - << std::endl; + logstream(LOG_EMPH) << "hybrid info: mirror [" + << high_mirror << " " + << low_mirror << " " + << (float(high_mirror)/(high_mirror+low_mirror)) << "]" + << std::endl; memory_info::log_usage("set vertex type done."); logstream(LOG_EMPH) << "set vertex type: " @@ -569,7 +589,7 @@ namespace graphlab { << " secs" << std::endl; } -//#endif +#endif } @@ -625,8 +645,7 @@ namespace graphlab { /* Construct local graph */ /* */ /**************************************************************************/ - { - // Add all the edges to the local graph + { // Add all the edges to the local graph graph.local_graph.reserve_edge_space(nedges + 1); foreach(const edge_buffer_record& rec, hybrid_edges) { @@ -664,8 +683,7 @@ namespace graphlab { graph.local_graph.num_vertices()); #ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("base::populating local graph done."); - logstream(LOG_EMPH) << "base::populating local graph: " + logstream(LOG_INFO) << "populating local graph: " << ti.current_time() << " secs" << std::endl; @@ -674,15 +692,14 @@ namespace graphlab { // Finalize local graph graph.local_graph.finalize(); #ifdef TUNING - logstream(LOG_INFO) << "base::local graph info: " << std::endl + logstream(LOG_INFO) << "local graph info: " << std::endl << "\t nverts: " << graph.local_graph.num_vertices() << std::endl << "\t nedges: " << graph.local_graph.num_edges() << std::endl; if(l_procid == 0) { - memory_info::log_usage("base::finalizing local graph done."); - logstream(LOG_EMPH) << "base::finalizing local graph: " + logstream(LOG_INFO) << "finalizing local graph: " << ti.current_time() << " secs" << std::endl; @@ -698,28 +715,51 @@ namespace graphlab { /**************************************************************************/ // Setup the map containing all the vertices being negotiated by this machine { - if (!standalone) { - // has flushed in finalize() - if (temporary_vertex_exchange.size() > 0) { - vertex_buffer_type vertex_buffer; procid_t sending_proc(-1); - while (temporary_vertex_exchange.recv(sending_proc, vertex_buffer)) { - foreach (const vertex_buffer_record& rec, vertex_buffer) { - if (mht.find(rec.vid) == mht.end()) - mht[rec.vid] = graph_hash::hash_vertex(rec.vid) % nprocs; - hybrid_vertex_exchange.send(mht[rec.vid], rec); + if (standalone) { + vertex_buffer_type vertex_buffer; + procid_t proc = -1; + while(hybrid_vertex_exchange.recv(proc, vertex_buffer)) { + foreach(const vertex_buffer_record& rec, vertex_buffer) { + lvid_type lvid(-1); + if (graph.vid2lvid.find(rec.vid) == graph.vid2lvid.end()) { + if (vid2lvid_buffer.find(rec.vid) == vid2lvid_buffer.end()) { + lvid = lvid_start + vid2lvid_buffer.size(); + vid2lvid_buffer[rec.vid] = lvid; + } else { + lvid = vid2lvid_buffer[rec.vid]; + } + } else { + lvid = graph.vid2lvid[rec.vid]; + updated_lvids.set_bit(lvid); + } + if (distributed_hybrid_ginger_ingress::vertex_combine_strategy + && lvid < graph.num_local_vertices()) { + distributed_hybrid_ginger_ingress::vertex_combine_strategy( + graph.l_vertex(lvid).data(), rec.vdata); + } else { + graph.local_graph.add_vertex(lvid, rec.vdata); } } - temporary_vertex_exchange.clear(); } + hybrid_vertex_exchange.clear(); + } + else { + // re-send by trampoline + vertex_buffer_type vertex_buffer; + procid_t proc = -1; + while (hybrid_vertex_exchange.recv(proc, vertex_buffer)) { + foreach (const vertex_buffer_record& rec, vertex_buffer) { + if (mht.find(rec.vid) == mht.end()) + mht[rec.vid] = graph_hash::hash_vertex(rec.vid) % nprocs; + resend_vertex_exchange.send(mht[rec.vid], rec); + } + } + hybrid_vertex_exchange.clear(); - // match standalone code - hybrid_vertex_exchange.flush(); - } - - // receive any vertex data sent by other machines - if (hybrid_vertex_exchange.size() > 0) { - vertex_buffer_type vertex_buffer; procid_t sending_proc(-1); - while(hybrid_vertex_exchange.recv(sending_proc, vertex_buffer)) { + // receive vertex data re-sent by other machines + resend_vertex_exchange.flush(); + proc = -1; + while(resend_vertex_exchange.recv(proc, vertex_buffer)) { foreach(const vertex_buffer_record& rec, vertex_buffer) { lvid_type lvid(-1); if (graph.vid2lvid.find(rec.vid) == graph.vid2lvid.end()) { @@ -742,22 +782,21 @@ namespace graphlab { } } } - hybrid_vertex_exchange.clear(); + resend_vertex_exchange.clear(); + } + #ifdef TUNING - logstream(LOG_INFO) << "base::#vert-msgs=" << hybrid_vertex_exchange.size() + if(l_procid == 0) { + logstream(LOG_INFO) << "adding vertex data: " + << ti.current_time() + << " secs" << std::endl; - if(l_procid == 0) { - memory_info::log_usage("base::adding vertex data done."); - logstream(LOG_EMPH) << "base::adding vertex data: " - << ti.current_time() - << " secs" - << std::endl; - } -#endif } +#endif } // end of loop to populate vrecmap + /**************************************************************************/ /* */ /* Assign vertex data and allocate vertex (meta)data space */ @@ -776,17 +815,15 @@ namespace graphlab { vrec.owner = 0; } else { if (mht.find(pair.first) == mht.end()) - mht[pair.first] = graph_hash::hash_vertex(pair.first) % nprocs; - const procid_t source_owner_proc = mht[pair.first]; - vrec.owner = source_owner_proc; + mht[pair.first] = graph_hash::hash_vertex(pair.first) % nprocs; + vrec.owner = mht[pair.first]; } } ASSERT_EQ(local_nverts, graph.local_graph.num_vertices()); ASSERT_EQ(graph.lvid2record.size(), graph.local_graph.num_vertices()); #ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("allocating lvid2record done."); - logstream(LOG_EMPH) << "allocating lvid2record: " + logstream(LOG_INFO) << "allocating lvid2record: " << ti.current_time() << " secs" << std::endl; @@ -855,7 +892,7 @@ namespace graphlab { vid_buffer.clear(); if (!flying_vids.empty()) { - logstream(LOG_INFO) << "base::#flying-own-nverts=" + logstream(LOG_INFO) << "#flying-own-nverts=" << flying_vids.size() << std::endl; @@ -879,8 +916,7 @@ namespace graphlab { #ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("base::master handshake done."); - logstream(LOG_EMPH) << "base::master handshake: " + logstream(LOG_INFO) << "master handshake: " << ti.current_time() << " secs" << std::endl; @@ -939,26 +975,33 @@ namespace graphlab { boost::bind(&distributed_hybrid_ginger_ingress::finalize_apply, this, _1, _2, _3)); vrecord_sync_gas.exec(changed_vset); +#ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("synchronizing vertex (meta)data done."); - logstream(LOG_EMPH) << "synchrionizing vertex (meta)data: " + logstream(LOG_INFO) << "synchrionizing vertex (meta)data: " << ti.current_time() << " secs" << std::endl; } +#endif } base_type::exchange_global_info(standalone); - #ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("exchange global info done."); - logstream(LOG_EMPH) << "exchange global info: " + logstream(LOG_INFO) << "exchange global info: " << ti.current_time() << " secs" << std::endl; } #endif + + if(l_procid == 0) { + memory_info::log_usage("base finalizing done."); + logstream(LOG_EMPH) << "base finalizing. (" + << ti.current_time() + << " secs)" + << std::endl; + } } // end of modified base finalize private: diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp index fca32c7c7b..13e20fd460 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp @@ -90,7 +90,7 @@ namespace graphlab { std::vector hybrid_edges; - /// one-by-one ingress. e.g., SNAP + /* ingress exchange */ buffered_exchange hybrid_edge_exchange; buffered_exchange hybrid_vertex_exchange; @@ -105,10 +105,10 @@ namespace graphlab { graph(graph), threshold(threshold), #ifdef _OPENMP hybrid_edge_exchange(dc, omp_get_max_threads()), - hybrid_vertex_exchange(dc, omp_get_max_threads()), + hybrid_vertex_exchange(dc, omp_get_max_threads()) #else hybrid_edge_exchange(dc), - hybrid_vertex_exchange(dc), + hybrid_vertex_exchange(dc) #endif { /* fast pass for standalone case. */ @@ -138,14 +138,13 @@ namespace graphlab { /* add vdata */ void add_vertex(vertex_id_type vid, const VertexData& vdata) { const vertex_buffer_record record(vid, vdata); - if (standalone) { - /* fast pass for redundant finalization with no graph changes. */ - hybrid_vertex_exchange.send(0, record); - } else { - const procid_t owning_proc = - graph_hash::hash_vertex(vid) % hybrid_rpc.numprocs(); - hybrid_vertex_exchange.send(owning_proc, record); - } + const procid_t owning_proc = standalone ? 0 : + graph_hash::hash_vertex(vid) % hybrid_rpc.numprocs(); +#ifdef _OPENMP + hybrid_vertex_exchange.send(owning_proc, record, omp_get_thread_num()); +#else + hybrid_vertex_exchange.send(owning_proc, record); +#endif } // end of add vertex @@ -157,6 +156,8 @@ namespace graphlab { procid_t l_procid = hybrid_rpc.procid(); size_t nedges = 0; + hybrid_rpc.full_barrier(); + if (l_procid == 0) { memory_info::log_usage("start finalizing"); logstream(LOG_EMPH) << "hybrid finalizing ..." @@ -189,7 +190,7 @@ namespace graphlab { /**************************************************************************/ /* */ - /* prepare hybrid ingress */ + /* Prepare hybrid ingress */ /* */ /**************************************************************************/ { @@ -202,8 +203,7 @@ namespace graphlab { proc = -1; while(hybrid_edge_exchange.recv(proc, edge_buffer)) foreach(const edge_buffer_record& rec, edge_buffer) - hybrid_edges.push_back(rec); - + hybrid_edges.push_back(rec); hybrid_edge_exchange.clear(); } else { hopscotch_map in_degree_set; @@ -216,11 +216,10 @@ namespace graphlab { } } hybrid_edge_exchange.clear(); - hybrid_rpc.full_barrier(); // sync before reusing + hybrid_edge_exchange.barrier(); // barrier before reusing #ifdef TUNING - if(l_procid == 0) { - memory_info::log_usage("save local edges and count in-degree done."); - logstream(LOG_EMPH) << "save local edges and count in-degree: " + if(l_procid == 0) { + logstream(LOG_INFO) << "save local edges and count in-degree: " << ti.current_time() << " secs" << std::endl; @@ -243,9 +242,8 @@ namespace graphlab { } } #ifdef TUNING - if(l_procid == 0) { - memory_info::log_usage("resend edges of high-degree vertices done."); - logstream(LOG_EMPH) << "resend edges of high-degree vertices: " + if(l_procid == 0) { + logstream(LOG_INFO) << "resend edges of high-degree vertices: " << ti.current_time() << " secs" << std::endl; @@ -255,9 +253,8 @@ namespace graphlab { // receive edges of high-degree vertices hybrid_edge_exchange.flush(); #ifdef TUNING - if(l_procid == 0) - logstream(LOG_INFO) << "receive high-degree edges: " - << hybrid_edge_exchange.size() << std::endl; + logstream(LOG_INFO) << "receive high-degree edges: " + << hybrid_edge_exchange.size() << std::endl; #endif proc = -1; while(hybrid_edge_exchange.recv(proc, edge_buffer)) { @@ -269,9 +266,8 @@ namespace graphlab { hybrid_edge_exchange.clear(); in_degree_set.clear(); #ifdef TUNING - if(l_procid == 0) { - memory_info::log_usage("receive high-degree edges done."); - logstream(LOG_EMPH) << "receive high-degree edges: " + if(l_procid == 0) { + logstream(LOG_INFO) << "receive high-degree edges: " << ti.current_time() << " secs" << std::endl; @@ -469,8 +465,7 @@ namespace graphlab { graph.local_graph.num_vertices()); #ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("populating local graph done."); - logstream(LOG_EMPH) << "populating local graph: " + logstream(LOG_INFO) << "populating local graph: " << ti.current_time() << " secs" << std::endl; @@ -486,8 +481,7 @@ namespace graphlab { << std::endl; if(l_procid == 0) { - memory_info::log_usage("finalizing local graph done."); - logstream(LOG_EMPH) << "finalizing local graph: " + logstream(LOG_INFO) << "finalizing local graph: " << ti.current_time() << " secs" << std::endl; @@ -533,9 +527,8 @@ namespace graphlab { #ifdef TUNING logstream(LOG_INFO) << "base::#vert-msgs=" << hybrid_vertex_exchange.size() << std::endl; - if(l_procid == 0) { - memory_info::log_usage("adding vertex data done."); - logstream(LOG_EMPH) << "adding vertex data: " + if(l_procid == 0) { + logstream(LOG_INFO) << "adding vertex data: " << ti.current_time() << " secs" << std::endl; @@ -568,8 +561,7 @@ namespace graphlab { ASSERT_EQ(graph.lvid2record.size(), graph.local_graph.num_vertices()); #ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("allocating lvid2record done."); - logstream(LOG_EMPH) << "allocating lvid2record: " + logstream(LOG_INFO) << "allocating lvid2record: " << ti.current_time() << " secs" << std::endl; @@ -662,8 +654,7 @@ namespace graphlab { #ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("master handshake done."); - logstream(LOG_EMPH) << "master handshake: " + logstream(LOG_INFO) << "master handshake: " << ti.current_time() << " secs" << std::endl; @@ -723,20 +714,20 @@ namespace graphlab { boost::bind(&distributed_hybrid_ingress::finalize_apply, this, _1, _2, _3)); vrecord_sync_gas.exec(changed_vset); +#ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("synchronizing vertex (meta)data done."); - logstream(LOG_EMPH) << "synchrionizing vertex (meta)data: " + logstream(LOG_INFO) << "synchrionizing vertex (meta)data: " << ti.current_time() << " secs" << std::endl; } +#endif } base_type::exchange_global_info(standalone); #ifdef TUNING if(l_procid == 0) { - memory_info::log_usage("exchange global info done."); - logstream(LOG_EMPH) << "exchange global info: " + logstream(LOG_INFO) << "exchange global info: " << ti.current_time() << " secs" << std::endl; diff --git a/src/graphlab/graph/ingress/ingress_edge_decision.hpp b/src/graphlab/graph/ingress/ingress_edge_decision.hpp index 093bb320a0..8419419e9a 100644 --- a/src/graphlab/graph/ingress/ingress_edge_decision.hpp +++ b/src/graphlab/graph/ingress/ingress_edge_decision.hpp @@ -70,15 +70,14 @@ namespace graphlab { }; /** Assign edges via a heuristic method called ginger */ - procid_t edge_to_proc_ginger ( - const vertex_id_type vid, + procid_t edge_to_proc_ginger (const std::vector& sources, + const vertex_id_type target, master_hash_table_type& mht, - master_hash_table_type& mht_incremental, - std::vector& proc_num_vertices, + master_hash_table_type& mht_incr, + std::vector& proc_balance, double alpha, - double gamma, - std::vector& in) { - size_t numprocs = proc_num_vertices.size(); + double gamma) { + size_t numprocs = proc_balance.size(); // Compute the score of each proc. procid_t best_proc = -1; @@ -86,27 +85,28 @@ namespace graphlab { std::vector proc_score(numprocs); std::vector proc_degrees(numprocs); - for (size_t i = 0; i < in.size(); ++i) { - if (mht.find(in[i]) != mht.end()) - proc_degrees[mht[in[i]]]++; - else if (mht_incremental.find(in[i]) != mht_incremental.end()) - proc_degrees[mht_incremental[in[i]]]++; + for (size_t i = 0; i < sources.size(); ++i) { + if (mht.find(sources[i]) != mht.end()) + proc_degrees[mht[sources[i]]]++; + else if (mht_incr.find(sources[i]) != mht_incr.end()) + proc_degrees[mht_incr[sources[i]]]++; } for (size_t i = 0; i < numprocs; ++i) { - proc_score[i] = proc_degrees[i] - alpha * gamma * pow(proc_num_vertices[i], gamma-1); + proc_score[i] = proc_degrees[i] + - alpha * gamma * pow(proc_balance[i], (gamma - 1)); } - maxscore = *std::max_element(proc_score.begin(), proc_score.end()); + // TODO: just use std::max + maxscore = *std::max_element(proc_score.begin(), proc_score.end()); for (size_t i = 0; i < numprocs; ++i) { if (proc_score[i] == maxscore) { best_proc = i; break; } } - - proc_num_vertices[best_proc]++; - + + proc_balance[best_proc]++; return best_proc; }; From 1705ddd9b7c8fec1d5b28e788231c958a1986795 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Mon, 25 Nov 2013 14:35:23 +0800 Subject: [PATCH 22/50] fix bug for oblivious and performance issue for random, grid and pds when parallel loading multiple graph files --- .../distributed_constrained_random_ingress.hpp | 4 ++++ .../graph/ingress/distributed_ingress_base.hpp | 16 +++++++++++++++- .../ingress/distributed_oblivious_ingress.hpp | 13 +++++++++++-- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/graphlab/graph/ingress/distributed_constrained_random_ingress.hpp b/src/graphlab/graph/ingress/distributed_constrained_random_ingress.hpp index 881faf4ea8..5f67d9cb3d 100644 --- a/src/graphlab/graph/ingress/distributed_constrained_random_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_constrained_random_ingress.hpp @@ -81,7 +81,11 @@ namespace graphlab { const edge_buffer_record record(source, target, edata); +#ifdef _OPENMP + base_type::edge_exchange.send(owning_proc, record, omp_get_thread_num()); +#else base_type::edge_exchange.send(owning_proc, record); +#endif } // end of add edge }; // end of distributed_constrained_random_ingress }; // end of namespace graphlab diff --git a/src/graphlab/graph/ingress/distributed_ingress_base.hpp b/src/graphlab/graph/ingress/distributed_ingress_base.hpp index c5907d1417..8f4c75222e 100644 --- a/src/graphlab/graph/ingress/distributed_ingress_base.hpp +++ b/src/graphlab/graph/ingress/distributed_ingress_base.hpp @@ -113,7 +113,13 @@ namespace graphlab { public: distributed_ingress_base(distributed_control& dc, graph_type& graph) : - rpc(dc, this), graph(graph), vertex_exchange(dc), edge_exchange(dc), + rpc(dc, this), graph(graph), +#ifdef _OPENMP + vertex_exchange(dc, omp_get_max_threads()), + edge_exchange(dc, omp_get_max_threads()), +#else + vertex_exchange(dc), edge_exchange(dc), +#endif edge_decision(dc) { rpc.barrier(); } // end of constructor @@ -126,7 +132,11 @@ namespace graphlab { const procid_t owning_proc = edge_decision.edge_to_proc_random(source, target, rpc.numprocs()); const edge_buffer_record record(source, target, edata); +#ifdef _OPENMP + edge_exchange.send(owning_proc, record, omp_get_thread_num()); +#else edge_exchange.send(owning_proc, record); +#endif } // end of add edge @@ -134,7 +144,11 @@ namespace graphlab { virtual void add_vertex(vertex_id_type vid, const VertexData& vdata) { const procid_t owning_proc = graph_hash::hash_vertex(vid) % rpc.numprocs(); const vertex_buffer_record record(vid, vdata); +#ifdef _OPENMP + vertex_exchange.send(owning_proc, record, omp_get_thread_num()); +#else vertex_exchange.send(owning_proc, record); +#endif } // end of add vertex diff --git a/src/graphlab/graph/ingress/distributed_oblivious_ingress.hpp b/src/graphlab/graph/ingress/distributed_oblivious_ingress.hpp index d74e01e97b..fd5355169d 100644 --- a/src/graphlab/graph/ingress/distributed_oblivious_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_oblivious_ingress.hpp @@ -32,6 +32,7 @@ #include #include #include +#include #include namespace graphlab { template @@ -64,8 +65,9 @@ namespace graphlab { /** Array of number of edges on each proc. */ std::vector proc_num_edges; - - /** Ingress tratis. */ + simple_spinlock obliv_lock; + + /** Ingress traits. */ bool usehash; bool userecent; @@ -82,12 +84,19 @@ namespace graphlab { /** Add an edge to the ingress object using oblivious greedy assignment. */ void add_edge(vertex_id_type source, vertex_id_type target, const EdgeData& edata) { + obliv_lock.lock(); dht[source]; dht[target]; const procid_t owning_proc = base_type::edge_decision.edge_to_proc_greedy(source, target, dht[source], dht[target], proc_num_edges, usehash, userecent); + obliv_lock.unlock(); + typedef typename base_type::edge_buffer_record edge_buffer_record; edge_buffer_record record(source, target, edata); +#ifdef _OPENMP + base_type::edge_exchange.send(owning_proc, record, omp_get_thread_num()); +#else base_type::edge_exchange.send(owning_proc, record); +#endif } // end of add edge virtual void finalize() { From d6411f5cad98106c8f98979b57dfed2830c52ddb Mon Sep 17 00:00:00 2001 From: sjx Date: Tue, 26 Nov 2013 10:50:53 +0800 Subject: [PATCH 23/50] support hash affinity migration for bipartite ingress --- src/graphlab/graph/distributed_graph.hpp | 37 ++- ...distributed_bipartite_affinity_ingress.hpp | 172 ++++++++++++ .../distributed_bipartite_hash_ingress.hpp | 89 ++++++ .../distributed_bipartite_hybrid_ingress.hpp | 264 ++++++++++++++++++ 4 files changed, 560 insertions(+), 2 deletions(-) create mode 100644 src/graphlab/graph/ingress/distributed_bipartite_affinity_ingress.hpp create mode 100644 src/graphlab/graph/ingress/distributed_bipartite_hash_ingress.hpp create mode 100644 src/graphlab/graph/ingress/distributed_bipartite_hybrid_ingress.hpp diff --git a/src/graphlab/graph/distributed_graph.hpp b/src/graphlab/graph/distributed_graph.hpp index 306e0e23f3..0706e3f998 100644 --- a/src/graphlab/graph/distributed_graph.hpp +++ b/src/graphlab/graph/distributed_graph.hpp @@ -82,6 +82,10 @@ #include #include +#include +#include +#include + #include #include @@ -404,6 +408,9 @@ namespace graphlab { friend class distributed_identity_ingress; friend class distributed_oblivious_ingress; friend class distributed_constrained_random_ingress; + friend class distributed_bipartite_hybrid_ingress; + friend class distributed_bipartite_hash_ingress; + friend class distributed_bipartite_affinity_ingress; friend class distributed_hybrid_ingress; friend class distributed_hybrid_ginger_ingress; @@ -656,6 +663,8 @@ namespace graphlab { size_t nverts = 0; std::string ingress_method = ""; + bool affinity = false; + std::string direction = "source"; std::vector keys = opts.get_graph_args().get_option_keys(); foreach(std::string opt, keys) { if (opt == "ingress") { @@ -688,6 +697,16 @@ namespace graphlab { if (rpc.procid() == 0) logstream(LOG_EMPH) << "Graph Option: nverts = " << nverts << std::endl; + } else if (opt == "affinity") { + opts.get_graph_args().get_option("affinity", affinity); + if (rpc.procid() == 0) + logstream(LOG_EMPH) << "Graph Option: affinity = " + << affinity << std::endl; + } else if (opt == "direction") { + opts.get_graph_args().get_option("direction", direction); + if (rpc.procid() == 0) + logstream(LOG_EMPH) << "Graph Option: direction = " + << direction << std::endl; } /** @@ -712,7 +731,7 @@ namespace graphlab { logstream(LOG_ERROR) << "Unexpected Graph Option: " << opt << std::endl; } } - set_ingress_method(ingress_method, bufsize, usehash, userecent, + set_ingress_method(ingress_method, bufsize, usehash, userecent,affinity,direction, threshold, nedges, nverts, interval); } @@ -3282,9 +3301,11 @@ namespace graphlab { lock_manager_type lock_manager; void set_ingress_method(const std::string& method, - size_t bufsize = 50000, bool usehash = false, bool userecent = false, + size_t bufsize = 50000, bool usehash = false, bool userecent = false,bool affinity=false,std::string direction="source", size_t threshold = 100, size_t nedges = 0, size_t nverts = 0, size_t interval = std::numeric_limits::max()) { + if(direction!="target") + direction="source"; if(ingress_ptr != NULL) { delete ingress_ptr; ingress_ptr = NULL; } if (method == "oblivious") { if (rpc.procid() == 0) logstream(LOG_EMPH) << "Use oblivious ingress, usehash: " << usehash @@ -3299,6 +3320,18 @@ namespace graphlab { } else if (method == "pds") { if (rpc.procid() == 0)logstream(LOG_EMPH) << "Use pds ingress" << std::endl; ingress_ptr = new distributed_constrained_random_ingress(rpc.dc(), *this, "pds"); + } else if (method == "bipartite") { + if(!affinity){ + if (rpc.procid() == 0) logstream(LOG_EMPH) << "Use bipartite ingress without affinity" << std::endl; + ingress_ptr = new distributed_bipartite_hash_ingress(rpc.dc(), *this, direction); + } + else{ + if (rpc.procid() == 0) logstream(LOG_EMPH) << "Use bipartite ingress with affinity" << std::endl; + ingress_ptr = new distributed_bipartite_affinity_ingress(rpc.dc(), *this, direction); + } + } else if (method == "bipartite_opt") { + if (rpc.procid() == 0) logstream(LOG_EMPH) << "Use bipartite_opt ingress" << std::endl; + ingress_ptr = new distributed_bipartite_hybrid_ingress(rpc.dc(), *this, direction); } else if (method == "hybrid") { if (rpc.procid() == 0) logstream(LOG_EMPH) << "Use hybrid ingress" << std::endl; ingress_ptr = new distributed_hybrid_ingress(rpc.dc(), *this, threshold); diff --git a/src/graphlab/graph/ingress/distributed_bipartite_affinity_ingress.hpp b/src/graphlab/graph/ingress/distributed_bipartite_affinity_ingress.hpp new file mode 100644 index 0000000000..9c778f2749 --- /dev/null +++ b/src/graphlab/graph/ingress/distributed_bipartite_affinity_ingress.hpp @@ -0,0 +1,172 @@ + + +#ifndef GRAPHLAB_DISTRIBUTED_BIPARTITE_AFFINITY_INGRESS_HPP +#define GRAPHLAB_DISTRIBUTED_BIPARTITE_AFFINITY_INGRESS_HPP + +#include + +#include +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include + +namespace graphlab { + template + class distributed_graph; + + /** + * \brief Ingress object assigning edges using randoming hash function. + */ + template + class distributed_bipartite_affinity_ingress : + public distributed_ingress_base { + public: + typedef distributed_graph graph_type; + /// The type of the vertex data stored in the graph + typedef VertexData vertex_data_type; + /// The type of the edge data stored in the graph + typedef EdgeData edge_data_type; + + + typedef distributed_ingress_base base_type; + + + typedef typename graph_type::vertex_record vertex_record; + + typedef typename base_type::edge_buffer_record edge_buffer_record; + typedef typename buffered_exchange::buffer_type + edge_buffer_type; + typedef typename base_type::vertex_buffer_record vertex_buffer_record; + typedef typename buffered_exchange::buffer_type + vertex_buffer_type; + + + graph_type& graph; + dc_dist_object bipartite_rpc; + + + typedef typename boost::unordered_map + master_hash_table_type; + typedef typename std::pair + master_pair_type; + typedef typename buffered_exchange::buffer_type + master_buffer_type; + + master_hash_table_type mht; + + + buffered_exchange bipartite_edge_exchange; + buffered_exchange master_exchange; + + bool source_is_special; + simple_spinlock bipartite_vertex_lock; + std::vector bipartite_vertexs; + simple_spinlock bipartite_edge_lock; + std::vector bipartite_edges; + + public: + distributed_bipartite_affinity_ingress(distributed_control& dc, graph_type& graph,const std::string& specialvertex): + base_type(dc, graph),graph(graph),bipartite_rpc(dc, this), + bipartite_edge_exchange(dc),master_exchange(dc) + { + if(specialvertex=="source") + source_is_special=true; + else + source_is_special=false; + } // end of constructor + + ~distributed_bipartite_affinity_ingress() { + } + + /** Add an edge to the ingress object using random assignment. */ + void add_edge(vertex_id_type source, vertex_id_type target, + const EdgeData& edata) { + const edge_buffer_record record(source, target, edata); + bipartite_edge_lock.lock(); + bipartite_edges.push_back(record); + bipartite_edge_lock.unlock(); + } // end of add edge + + void add_vertex(vertex_id_type vid, const VertexData& vdata) { + const vertex_buffer_record record(vid, vdata); + bipartite_vertex_lock.lock(); + bipartite_vertexs.push_back(record); + mht[vid]=bipartite_rpc.procid(); + bipartite_vertex_lock.unlock(); + } // end of add vertex + + void finalize() { + + { + size_t changed_size = bipartite_vertexs.size() + bipartite_edges.size(); + bipartite_rpc.all_reduce(changed_size); + if (changed_size == 0) { + logstream(LOG_INFO) << "Skipping Graph Finalization because no changes happened..." << std::endl; + return; + } + } + + graph.lvid2record.resize(bipartite_vertexs.size()); + graph.local_graph.resize(bipartite_vertexs.size()); + lvid_type lvid = 0; + foreach(const vertex_buffer_record& rec,bipartite_vertexs){ + graph.vid2lvid[rec.vid]=lvid; + graph.local_graph.add_vertex(lvid,rec.vdata); + vertex_record& vrec = graph.lvid2record[lvid]; + vrec.gvid = rec.vid; + vrec.owner=bipartite_rpc.procid(); + lvid++; + } + + for (typename master_hash_table_type::iterator it = mht.begin() ; it != mht.end(); ++it) { + for (procid_t i = 0; i < bipartite_rpc.numprocs(); ++i) { + if (i != bipartite_rpc.procid()) + master_exchange.send(i, master_pair_type(it->first, it->second)); + } + } + master_exchange.flush(); + master_buffer_type master_buffer; + procid_t proc = -1; + while(master_exchange.recv(proc, master_buffer)) { + foreach(const master_pair_type& pair, master_buffer) { + mht[pair.first] = pair.second; + } + } + master_exchange.clear(); + + foreach(const edge_buffer_record& rec,bipartite_edges){ + if(source_is_special){ + if(mht.find(rec.source)==mht.end()) + mht[rec.source]=graph_hash::hash_vertex(rec.source) % bipartite_rpc.numprocs(); + const procid_t owning_proc = mht[rec.source] ; + base_type::edge_exchange.send(owning_proc, rec); + } + else{ + if(mht.find(rec.target)==mht.end()) + mht[rec.target]=graph_hash::hash_vertex(rec.target) % bipartite_rpc.numprocs(); + const procid_t owning_proc = mht[rec.target] ; + base_type::edge_exchange.send(owning_proc, rec); + } + } + bipartite_vertexs.clear(); + bipartite_edges.clear(); + + base_type::finalize(); + + } // end of finalize + + }; // end of distributed_bipartite_affinity_ingress +}; // end of namespace graphlab +#include + + +#endif diff --git a/src/graphlab/graph/ingress/distributed_bipartite_hash_ingress.hpp b/src/graphlab/graph/ingress/distributed_bipartite_hash_ingress.hpp new file mode 100644 index 0000000000..8da8096fb2 --- /dev/null +++ b/src/graphlab/graph/ingress/distributed_bipartite_hash_ingress.hpp @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2009 Carnegie Mellon University. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * For more about this software visit: + * + * http://www.graphlab.ml.cmu.edu + * + */ + +#ifndef GRAPHLAB_DISTRIBUTED_BIPARTITE_HASH_INGRESS_HPP +#define GRAPHLAB_DISTRIBUTED_BIPARTITE_HASH_INGRESS_HPP + +#include + +#include +#include +#include +#include + + +#include +namespace graphlab { + template + class distributed_graph; + + /** + * \brief Ingress object assigning edges using randoming hash function. + */ + template + class distributed_bipartite_hash_ingress : + public distributed_ingress_base { + public: + typedef distributed_graph graph_type; + /// The type of the vertex data stored in the graph + typedef VertexData vertex_data_type; + /// The type of the edge data stored in the graph + typedef EdgeData edge_data_type; + + + typedef distributed_ingress_base base_type; + bool source_is_special; + public: + distributed_bipartite_hash_ingress(distributed_control& dc, graph_type& graph, + const std::string& specialvertex) : + base_type(dc, graph) { + if(specialvertex=="source") + source_is_special=true; + else + source_is_special=false; + } // end of constructor + + ~distributed_bipartite_hash_ingress() { + + } + + /** Add an edge to the ingress object using source or target assignment. */ + void add_edge(vertex_id_type source, vertex_id_type target, + const EdgeData& edata) { + typedef typename base_type::edge_buffer_record edge_buffer_record; + if(source_is_special){ + const procid_t owning_proc = graph_hash::hash_vertex(source) % base_type::rpc.numprocs(); + const edge_buffer_record record(source, target, edata); + base_type::edge_exchange.send(owning_proc, record); + } + else{ + const procid_t owning_proc = graph_hash::hash_vertex(target) % base_type::rpc.numprocs(); + const edge_buffer_record record(source, target, edata); + base_type::edge_exchange.send(owning_proc, record); + } + } // end of add edge + }; // end of distributed_bipartite_hash_ingress +}; // end of namespace graphlab +#include + + +#endif diff --git a/src/graphlab/graph/ingress/distributed_bipartite_hybrid_ingress.hpp b/src/graphlab/graph/ingress/distributed_bipartite_hybrid_ingress.hpp new file mode 100644 index 0000000000..c336d13e55 --- /dev/null +++ b/src/graphlab/graph/ingress/distributed_bipartite_hybrid_ingress.hpp @@ -0,0 +1,264 @@ + + +#ifndef GRAPHLAB_DISTRIBUTED_BIPARTITE_HYBRID_INGRESS_HPP +#define GRAPHLAB_DISTRIBUTED_BIPARTITE_HYBRID_INGRESS_HPP + +#include + +#include +#include +#include +#include +#include +#include + + +#include +#include +#include +#include + +namespace graphlab { + template + class distributed_graph; + + /** + * \brief Ingress object assigning edges using randoming hash function. + */ + template + class distributed_bipartite_hybrid_ingress : + public distributed_ingress_base { + public: + typedef distributed_graph graph_type; + /// The type of the vertex data stored in the graph + typedef VertexData vertex_data_type; + /// The type of the edge data stored in the graph + typedef EdgeData edge_data_type; + + + typedef distributed_ingress_base base_type; + + + typedef typename graph_type::vertex_record vertex_record; + + typedef typename base_type::edge_buffer_record edge_buffer_record; + typedef typename buffered_exchange::buffer_type + edge_buffer_type; + typedef typename base_type::vertex_buffer_record vertex_buffer_record; + typedef typename buffered_exchange::buffer_type + vertex_buffer_type; + typedef typename std::pair edge_msg; + typedef typename buffered_exchange::buffer_type + edge_msg_type; + + + graph_type& graph; + dc_dist_object hybrid_rpc; + + buffered_exchange hybrid_vertex_exchange; + buffered_exchange hybrid_edge_exchange; + buffered_exchange hybrid_msg_exchange; + + + std::vector hybrid_edges; + + bool source_is_special; + + + + public: + distributed_bipartite_hybrid_ingress(distributed_control& dc, graph_type& graph,const std::string& specialvertex) : + base_type(dc, graph),graph(graph),hybrid_rpc(dc, this), + hybrid_vertex_exchange(dc),hybrid_edge_exchange(dc),hybrid_msg_exchange(dc) + { + + if(specialvertex=="source") + source_is_special=true; + else + source_is_special=false; + + } // end of constructor + + ~distributed_bipartite_hybrid_ingress() { + + } + + /** Add an edge to the ingress object using random assignment. */ + void add_edge(vertex_id_type source, vertex_id_type target, + const EdgeData& edata) { + const edge_buffer_record record(source, target, edata); + if(source_is_special){ + const procid_t owning_proc = + graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); + hybrid_edge_exchange.send(owning_proc, record); + } + else{ + const procid_t owning_proc = + graph_hash::hash_vertex(source) % hybrid_rpc.numprocs(); + hybrid_edge_exchange.send(owning_proc, record); + } + } // end of add edge + + void add_vertex(vertex_id_type vid, const VertexData& vdata) { + const vertex_buffer_record record(vid, vdata); + const procid_t owning_proc = + graph_hash::hash_vertex(vid) % hybrid_rpc.numprocs(); + hybrid_vertex_exchange.send(owning_proc, record); + } // end of add vertex + + void finalize() { + + + edge_buffer_type edge_buffer; + + hopscotch_map in_degree_set; + procid_t proc; + + hybrid_edge_exchange.flush(); + hybrid_vertex_exchange.flush(); + + { + size_t changed_size = hybrid_edge_exchange.size() + hybrid_vertex_exchange.size(); + hybrid_rpc.all_reduce(changed_size); + if (changed_size == 0) { + logstream(LOG_INFO) << "Skipping Graph Finalization because no changes happened..." << std::endl; + return; + } + } + + proc = -1; + while(hybrid_edge_exchange.recv(proc, edge_buffer)) { + foreach(const edge_buffer_record& rec, edge_buffer) { + hybrid_edges.push_back(rec); + if(source_is_special) + in_degree_set[rec.target]++; + else + in_degree_set[rec.source]++; + } + } + hybrid_edge_exchange.clear(); + hybrid_edge_exchange.barrier(); // sync before reusing + + // re-send edges of high-degree vertices + for (size_t i = 0; i < hybrid_edges.size(); i++) { + edge_buffer_record& rec = hybrid_edges[i]; + if(source_is_special){ + const procid_t owner_proc = + graph_hash::hash_vertex(rec.source) % hybrid_rpc.numprocs(); + hybrid_msg_exchange.send(owner_proc,std::make_pair(rec,in_degree_set[rec.target])); + } + else{ + const procid_t owner_proc = + graph_hash::hash_vertex(rec.target) % hybrid_rpc.numprocs(); + hybrid_msg_exchange.send(owner_proc,std::make_pair(rec,in_degree_set[rec.source])); + } + } + in_degree_set.clear(); + hybrid_edges.clear(); + hybrid_msg_exchange.flush(); + std::map > count_map; + std::map owner_map; + + proc = -1; + + edge_msg_type msg_buffer; + if(source_is_special){ + while(hybrid_msg_exchange.recv(proc, msg_buffer)) { + foreach(const edge_msg& msg, msg_buffer) { + + hybrid_edges.push_back(msg.first); + if(count_map.find(msg.first.source)==count_map.end()){ + count_map[msg.first.source].resize(hybrid_rpc.numprocs()); + owner_map[msg.first.source]=hybrid_rpc.procid(); + } + const procid_t target_proc = + graph_hash::hash_vertex(msg.first.target) % hybrid_rpc.numprocs(); + //count_map[msg.first.source][target_proc]+=1.0/(msg.second+1); + count_map[msg.first.source][target_proc]+=1.0;///(msg.second+1); + const procid_t max_proc = owner_map[msg.first.source]; + if(count_map[msg.first.source][target_proc] > count_map[msg.first.source][max_proc]) + owner_map[msg.first.source]=target_proc; + } + } + } + else{ + while(hybrid_msg_exchange.recv(proc, msg_buffer)) { + foreach(const edge_msg& msg, msg_buffer) { + + hybrid_edges.push_back(msg.first); + if(count_map.find(msg.first.target)==count_map.end()){ + count_map[msg.first.target].resize(hybrid_rpc.numprocs()); + owner_map[msg.first.target]=hybrid_rpc.procid(); + } + const procid_t source_proc = + graph_hash::hash_vertex(msg.first.source) % hybrid_rpc.numprocs(); + count_map[msg.first.target][source_proc]+=1.0;///(msg.second+1); + const procid_t max_proc = owner_map[msg.first.target]; + if(count_map[msg.first.target][source_proc] > count_map[msg.first.target][max_proc]) + owner_map[msg.first.target]=source_proc; + } + } + } + + // re-send + for (size_t i = 0; i < hybrid_edges.size(); i++) { + edge_buffer_record& rec = hybrid_edges[i]; + procid_t owner_proc ; + if(source_is_special) + owner_proc = owner_map[rec.source]; + else + owner_proc = owner_map[rec.target]; + hybrid_edge_exchange.send(owner_proc,rec); + } + hybrid_edge_exchange.flush(); + + std::set master_set; + proc = -1; + while(hybrid_edge_exchange.recv(proc, edge_buffer)) { + foreach(const edge_buffer_record& rec, edge_buffer) { + //hybrid_edges.push_back(rec); + if(source_is_special) + master_set.insert(rec.source); + else + master_set.insert(rec.target); + base_type::edge_exchange.send(hybrid_rpc.procid(), rec); + } + } + hybrid_edge_exchange.clear(); + + graph.lvid2record.resize(master_set.size()); + graph.local_graph.resize(master_set.size()); + lvid_type lvid = 0; + foreach(const vertex_id_type& vid,master_set){ + graph.vid2lvid[vid]=lvid; + //graph.local_graph.add_vertex(lvid,vdata()); + vertex_record& vrec = graph.lvid2record[lvid]; + vrec.gvid = vid; + vrec.owner=hybrid_rpc.procid(); + lvid++; + } + + { // Receive any vertex data sent by other machines + vertex_buffer_type vertex_buffer; procid_t sending_proc(-1); + while(hybrid_vertex_exchange.recv(sending_proc, vertex_buffer)) { + foreach(const vertex_buffer_record& rec, vertex_buffer) { + if(owner_map.find(rec.vid) !=owner_map.end()){ + base_type::vertex_exchange.send(owner_map[rec.vid],rec); + } + else{ + base_type::vertex_exchange.send(hybrid_rpc.procid(),rec); + } + } + } + hybrid_vertex_exchange.clear(); + } // end of loop to populate vrecmap + + base_type::finalize(); + } // end of finalize + + }; // end of distributed_bipartite_hybrid_ingress +}; // end of namespace graphlab +#include + + +#endif From f98dd486387496d0fd97c5ed6b0dc5648964a462 Mon Sep 17 00:00:00 2001 From: sjx Date: Wed, 27 Nov 2013 19:14:29 +0800 Subject: [PATCH 24/50] load balance support for hybrid_opt and print some network msg --- .../distributed_bipartite_hybrid_ingress.hpp | 155 ++++++++---------- toolkits/collaborative_filtering/als.cpp | 33 ++++ toolkits/collaborative_filtering/sgd.cpp | 33 ++++ toolkits/collaborative_filtering/svd.cpp | 41 ++++- 4 files changed, 175 insertions(+), 87 deletions(-) diff --git a/src/graphlab/graph/ingress/distributed_bipartite_hybrid_ingress.hpp b/src/graphlab/graph/ingress/distributed_bipartite_hybrid_ingress.hpp index c336d13e55..5b3a7abbbf 100644 --- a/src/graphlab/graph/ingress/distributed_bipartite_hybrid_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_bipartite_hybrid_ingress.hpp @@ -47,9 +47,7 @@ namespace graphlab { typedef typename base_type::vertex_buffer_record vertex_buffer_record; typedef typename buffered_exchange::buffer_type vertex_buffer_type; - typedef typename std::pair edge_msg; - typedef typename buffered_exchange::buffer_type - edge_msg_type; + graph_type& graph; @@ -57,7 +55,6 @@ namespace graphlab { buffered_exchange hybrid_vertex_exchange; buffered_exchange hybrid_edge_exchange; - buffered_exchange hybrid_msg_exchange; std::vector hybrid_edges; @@ -69,7 +66,7 @@ namespace graphlab { public: distributed_bipartite_hybrid_ingress(distributed_control& dc, graph_type& graph,const std::string& specialvertex) : base_type(dc, graph),graph(graph),hybrid_rpc(dc, this), - hybrid_vertex_exchange(dc),hybrid_edge_exchange(dc),hybrid_msg_exchange(dc) + hybrid_vertex_exchange(dc),hybrid_edge_exchange(dc) { if(specialvertex=="source") @@ -89,12 +86,12 @@ namespace graphlab { const edge_buffer_record record(source, target, edata); if(source_is_special){ const procid_t owning_proc = - graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); + graph_hash::hash_vertex(source) % hybrid_rpc.numprocs(); hybrid_edge_exchange.send(owning_proc, record); } else{ const procid_t owning_proc = - graph_hash::hash_vertex(source) % hybrid_rpc.numprocs(); + graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); hybrid_edge_exchange.send(owning_proc, record); } } // end of add edge @@ -111,12 +108,18 @@ namespace graphlab { edge_buffer_type edge_buffer; - hopscotch_map in_degree_set; procid_t proc; hybrid_edge_exchange.flush(); hybrid_vertex_exchange.flush(); - + //std::map > count_map; + boost::unordered_map > count_map; + //std::map owner_map; + boost::unordered_map owner_map; + std::set master_set; + + std::vector proc_num_edges; + { size_t changed_size = hybrid_edge_exchange.size() + hybrid_vertex_exchange.size(); hybrid_rpc.all_reduce(changed_size); @@ -126,81 +129,78 @@ namespace graphlab { } } + proc_num_edges.resize(hybrid_rpc.numprocs()); proc = -1; while(hybrid_edge_exchange.recv(proc, edge_buffer)) { foreach(const edge_buffer_record& rec, edge_buffer) { hybrid_edges.push_back(rec); - if(source_is_special) - in_degree_set[rec.target]++; - else - in_degree_set[rec.source]++; + if(source_is_special){ + if(count_map.find(rec.source)==count_map.end()){ + count_map[rec.source].resize(hybrid_rpc.numprocs()); + //owner_map[rec.source]=hybrid_rpc.procid(); + } + const procid_t target_proc = + graph_hash::hash_vertex(rec.target) % hybrid_rpc.numprocs(); + count_map[rec.source][target_proc]+=1;///(msg.second+1); + //const procid_t max_proc = owner_map[rec.source]; + //if(count_map[rec.source][target_proc] > count_map[rec.source][max_proc]) + //owner_map[rec.source]=target_proc; + } else { + if(count_map.find(rec.target)==count_map.end()){ + count_map[rec.target].resize(hybrid_rpc.numprocs()); + //owner_map[rec.target]=hybrid_rpc.procid(); + } + const procid_t source_proc = + graph_hash::hash_vertex(rec.source) % hybrid_rpc.numprocs(); + count_map[rec.target][source_proc]+=1;///(msg.second+1); + //const procid_t max_proc = owner_map[rec.target]; + //if(count_map[rec.target][source_proc] > count_map[rec.target][max_proc]) + // owner_map[rec.target]=source_proc; + } } } hybrid_edge_exchange.clear(); - hybrid_edge_exchange.barrier(); // sync before reusing - - // re-send edges of high-degree vertices - for (size_t i = 0; i < hybrid_edges.size(); i++) { - edge_buffer_record& rec = hybrid_edges[i]; - if(source_is_special){ - const procid_t owner_proc = - graph_hash::hash_vertex(rec.source) % hybrid_rpc.numprocs(); - hybrid_msg_exchange.send(owner_proc,std::make_pair(rec,in_degree_set[rec.target])); - } - else{ - const procid_t owner_proc = - graph_hash::hash_vertex(rec.target) % hybrid_rpc.numprocs(); - hybrid_msg_exchange.send(owner_proc,std::make_pair(rec,in_degree_set[rec.source])); - } - } - in_degree_set.clear(); - hybrid_edges.clear(); - hybrid_msg_exchange.flush(); - std::map > count_map; - std::map owner_map; - - proc = -1; - edge_msg_type msg_buffer; - if(source_is_special){ - while(hybrid_msg_exchange.recv(proc, msg_buffer)) { - foreach(const edge_msg& msg, msg_buffer) { - - hybrid_edges.push_back(msg.first); - if(count_map.find(msg.first.source)==count_map.end()){ - count_map[msg.first.source].resize(hybrid_rpc.numprocs()); - owner_map[msg.first.source]=hybrid_rpc.procid(); - } - const procid_t target_proc = - graph_hash::hash_vertex(msg.first.target) % hybrid_rpc.numprocs(); - //count_map[msg.first.source][target_proc]+=1.0/(msg.second+1); - count_map[msg.first.source][target_proc]+=1.0;///(msg.second+1); - const procid_t max_proc = owner_map[msg.first.source]; - if(count_map[msg.first.source][target_proc] > count_map[msg.first.source][max_proc]) - owner_map[msg.first.source]=target_proc; + proc = -1; + buffered_exchange vid_buffer(hybrid_rpc.dc()); + //for (typename boost::unordered_map::iterator it = owner_map.begin(); + // it != owner_map.end(); ++it){ + // vid_buffer.send(it->second, it->first); + for (typename boost::unordered_map >::iterator it = count_map.begin(); + it != count_map.end(); ++it){ + procid_t maxproc=hybrid_rpc.procid(); + double maxscore =(it->second)[maxproc]-sqrt(1.0*proc_num_edges[maxproc]); + for(size_t i=0; i < hybrid_rpc.numprocs();i++){ + double current_score=(it->second)[i]-sqrt(1.0*proc_num_edges[i]); + if(current_score > maxscore){ + maxscore =current_score; + maxproc =i; } } + for(size_t i=0; i < hybrid_rpc.numprocs();i++){ + proc_num_edges[maxproc]+=(it->second)[i]; + } + owner_map[it->first]=maxproc; + vid_buffer.send(maxproc, it->first); } - else{ - while(hybrid_msg_exchange.recv(proc, msg_buffer)) { - foreach(const edge_msg& msg, msg_buffer) { - - hybrid_edges.push_back(msg.first); - if(count_map.find(msg.first.target)==count_map.end()){ - count_map[msg.first.target].resize(hybrid_rpc.numprocs()); - owner_map[msg.first.target]=hybrid_rpc.procid(); - } - const procid_t source_proc = - graph_hash::hash_vertex(msg.first.source) % hybrid_rpc.numprocs(); - count_map[msg.first.target][source_proc]+=1.0;///(msg.second+1); - const procid_t max_proc = owner_map[msg.first.target]; - if(count_map[msg.first.target][source_proc] > count_map[msg.first.target][max_proc]) - owner_map[msg.first.target]=source_proc; + + vid_buffer.flush(); + { + typename buffered_exchange::buffer_type buffer; + procid_t recvid = -1; + while(vid_buffer.recv(recvid, buffer)) { + foreach(const vertex_id_type vid, buffer) { + if(source_is_special) + master_set.insert(vid); + else + master_set.insert(vid); } } } + vid_buffer.clear(); - // re-send + + // re-send edges for (size_t i = 0; i < hybrid_edges.size(); i++) { edge_buffer_record& rec = hybrid_edges[i]; procid_t owner_proc ; @@ -208,24 +208,9 @@ namespace graphlab { owner_proc = owner_map[rec.source]; else owner_proc = owner_map[rec.target]; - hybrid_edge_exchange.send(owner_proc,rec); + base_type::edge_exchange.send(owner_proc,rec); } - hybrid_edge_exchange.flush(); - std::set master_set; - proc = -1; - while(hybrid_edge_exchange.recv(proc, edge_buffer)) { - foreach(const edge_buffer_record& rec, edge_buffer) { - //hybrid_edges.push_back(rec); - if(source_is_special) - master_set.insert(rec.source); - else - master_set.insert(rec.target); - base_type::edge_exchange.send(hybrid_rpc.procid(), rec); - } - } - hybrid_edge_exchange.clear(); - graph.lvid2record.resize(master_set.size()); graph.local_graph.resize(master_set.size()); lvid_type lvid = 0; diff --git a/toolkits/collaborative_filtering/als.cpp b/toolkits/collaborative_filtering/als.cpp index 613fc8317f..836e5fbc6c 100644 --- a/toolkits/collaborative_filtering/als.cpp +++ b/toolkits/collaborative_filtering/als.cpp @@ -619,6 +619,17 @@ int main(int argc, char** argv) { dc.cout() << "Loading graph. Finished in " << timer.current_time() << std::endl; + size_t bytes_sent=dc.bytes_sent(); + size_t calls_sent=dc.calls_sent(); + size_t network_bytes_sent=dc.network_bytes_sent(); + size_t bytes_received=dc.bytes_received(); + size_t calls_received=dc.calls_received(); + dc.cout() << "load_Bytes_Sent: " << bytes_sent << std::endl; + dc.cout() << "load_Calls_Sent: " << calls_sent << std::endl; + dc.cout() << "load_Network_Sent: " << network_bytes_sent<< std::endl; + dc.cout() << "load_Bytes_Received: " << bytes_received << std::endl; + dc.cout() << "load_Calls_Received: " << calls_received << std::endl; + if (dc.procid() == 0) add_implicit_edges(implicitratingtype, graph, dc); @@ -628,6 +639,16 @@ int main(int argc, char** argv) { dc.cout() << "Finalizing graph. Finished in " << timer.current_time() << std::endl; + bytes_sent=dc.bytes_sent()-bytes_sent; + calls_sent=dc.calls_sent()-calls_sent; + network_bytes_sent=dc.network_bytes_sent()-network_bytes_sent; + bytes_received=dc.bytes_received()-bytes_received; + calls_received=dc.calls_received()-calls_received; + dc.cout() << "finalize_Bytes_Sent: " << bytes_sent << std::endl; + dc.cout() << "finalize_Calls_Sent: " << calls_sent << std::endl; + dc.cout() << "finalize_Network_Sent: " << network_bytes_sent<< std::endl; + dc.cout() << "finalize_Bytes_Received: " << bytes_received << std::endl; + dc.cout() << "finalize_Calls_Received: " << calls_received << std::endl; dc.cout() << "========== Graph statistics on proc " << dc.procid() @@ -681,6 +702,18 @@ int main(int argc, char** argv) { dc.cout() << "Final error: " << std::endl; engine.aggregate_now("error"); + bytes_sent=dc.bytes_sent()-bytes_sent; + calls_sent=dc.calls_sent()-calls_sent; + network_bytes_sent=dc.network_bytes_sent()-network_bytes_sent; + bytes_received=dc.bytes_received()-bytes_received; + calls_received=dc.calls_received()-calls_received; + dc.cout() << "compute_Bytes_Sent: " << bytes_sent << std::endl; + dc.cout() << "compute_Calls_Sent: " << calls_sent << std::endl; + dc.cout() << "compute_Network_Sent: " << network_bytes_sent<< std::endl; + dc.cout() << "compute_Bytes_Received: " << bytes_received << std::endl; + dc.cout() << "compute_Calls_Received: " << calls_received << std::endl; + + // Make predictions --------------------------------------------------------- if(!predictions.empty()) { std::cout << "Saving predictions" << std::endl; diff --git a/toolkits/collaborative_filtering/sgd.cpp b/toolkits/collaborative_filtering/sgd.cpp index d73d035449..7a3219f06b 100644 --- a/toolkits/collaborative_filtering/sgd.cpp +++ b/toolkits/collaborative_filtering/sgd.cpp @@ -582,6 +582,17 @@ class sgd_vertex_program : dc.cout() << "Loading graph. Finished in " << timer.current_time() << std::endl; + size_t bytes_sent=dc.bytes_sent(); + size_t calls_sent=dc.calls_sent(); + size_t network_bytes_sent=dc.network_bytes_sent(); + size_t bytes_received=dc.bytes_received(); + size_t calls_received=dc.calls_received(); + dc.cout() << "load_Bytes_Sent: " << bytes_sent << std::endl; + dc.cout() << "load_Calls_Sent: " << calls_sent << std::endl; + dc.cout() << "load_Network_Sent: " << network_bytes_sent<< std::endl; + dc.cout() << "load_Bytes_Received: " << bytes_received << std::endl; + dc.cout() << "load_Calls_Received: " << calls_received << std::endl; + if (dc.procid() == 0) add_implicit_edges(implicitratingtype, graph, dc); @@ -592,6 +603,16 @@ class sgd_vertex_program : dc.cout() << "Finalizing graph. Finished in " << timer.current_time() << std::endl; + bytes_sent=dc.bytes_sent()-bytes_sent; + calls_sent=dc.calls_sent()-calls_sent; + network_bytes_sent=dc.network_bytes_sent()-network_bytes_sent; + bytes_received=dc.bytes_received()-bytes_received; + calls_received=dc.calls_received()-calls_received; + dc.cout() << "finalize_Bytes_Sent: " << bytes_sent << std::endl; + dc.cout() << "finalize_Calls_Sent: " << calls_sent << std::endl; + dc.cout() << "finalize_Network_Sent: " << network_bytes_sent<< std::endl; + dc.cout() << "finalize_Bytes_Received: " << bytes_received << std::endl; + dc.cout() << "finalize_Calls_Received: " << calls_received << std::endl; dc.cout() << "========== Graph statistics on proc " << dc.procid() @@ -649,6 +670,18 @@ class sgd_vertex_program : // Compute the final training error ----------------------------------------- dc.cout() << "Final error: " << std::endl; engine.aggregate_now("error"); + + bytes_sent=dc.bytes_sent()-bytes_sent; + calls_sent=dc.calls_sent()-calls_sent; + network_bytes_sent=dc.network_bytes_sent()-network_bytes_sent; + bytes_received=dc.bytes_received()-bytes_received; + calls_received=dc.calls_received()-calls_received; + dc.cout() << "compute_Bytes_Sent: " << bytes_sent << std::endl; + dc.cout() << "compute_Calls_Sent: " << calls_sent << std::endl; + dc.cout() << "compute_Network_Sent: " << network_bytes_sent<< std::endl; + dc.cout() << "compute_Bytes_Received: " << bytes_received << std::endl; + dc.cout() << "compute_Calls_Received: " << calls_received << std::endl; + // Make predictions --------------------------------------------------------- if(!predictions.empty()) { diff --git a/toolkits/collaborative_filtering/svd.cpp b/toolkits/collaborative_filtering/svd.cpp index ec61ed65cd..fe91ef2d55 100644 --- a/toolkits/collaborative_filtering/svd.cpp +++ b/toolkits/collaborative_filtering/svd.cpp @@ -599,7 +599,7 @@ void write_output_vector(const std::string datafile, const vec & output, bool is int main(int argc, char** argv) { global_logger().set_log_to_console(true); - + global_logger().set_log_level(LOG_INFO); INITIALIZE_TRACER(svd_bidiagonal, "svd bidiagonal"); INITIALIZE_TRACER(svd_error_estimate, "svd error estimate"); INITIALIZE_TRACER(svd_swork, "Svd swork"); @@ -700,6 +700,22 @@ int main(int argc, char** argv) { pgraph = &graph; dc.cout() << "Loading graph. Finished in " << timer.current_time() << std::endl; + + + + + + size_t bytes_sent=dc.bytes_sent(); + size_t calls_sent=dc.calls_sent(); + size_t network_bytes_sent=dc.network_bytes_sent(); + size_t bytes_received=dc.bytes_received(); + size_t calls_received=dc.calls_received(); + dc.cout() << "load_Bytes_Sent: " << bytes_sent << std::endl; + dc.cout() << "load_Calls_Sent: " << calls_sent << std::endl; + dc.cout() << "load_Network_Sent: " << network_bytes_sent<< std::endl; + dc.cout() << "load_Bytes_Received: " << bytes_received << std::endl; + dc.cout() << "load_Calls_Received: " << calls_received << std::endl; + dc.cout() << "Finalizing graph." << std::endl; timer.start(); graph.finalize(); @@ -707,6 +723,18 @@ int main(int argc, char** argv) { << timer.current_time() << std::endl; + bytes_sent=dc.bytes_sent()-bytes_sent; + calls_sent=dc.calls_sent()-calls_sent; + network_bytes_sent=dc.network_bytes_sent()-network_bytes_sent; + bytes_received=dc.bytes_received()-bytes_received; + calls_received=dc.calls_received()-calls_received; + dc.cout() << "finalize_Bytes_Sent: " << bytes_sent << std::endl; + dc.cout() << "finalize_Calls_Sent: " << calls_sent << std::endl; + dc.cout() << "finalize_Network_Sent: " << network_bytes_sent<< std::endl; + dc.cout() << "finalize_Bytes_Received: " << bytes_received << std::endl; + dc.cout() << "finalize_Calls_Received: " << calls_received << std::endl; + + dc.cout() << "========== Graph statistics on proc " << dc.procid() << " ===============" @@ -785,7 +813,16 @@ int main(int argc, char** argv) { assert(pow(singular_values[2]- 0.554159, 2) < 1e-8); } - + bytes_sent=dc.bytes_sent()-bytes_sent; + calls_sent=dc.calls_sent()-calls_sent; + network_bytes_sent=dc.network_bytes_sent()-network_bytes_sent; + bytes_received=dc.bytes_received()-bytes_received; + calls_received=dc.calls_received()-calls_received; + dc.cout() << "compute_Bytes_Sent: " << bytes_sent << std::endl; + dc.cout() << "compute_Calls_Sent: " << calls_sent << std::endl; + dc.cout() << "compute_Network_Sent: " << network_bytes_sent<< std::endl; + dc.cout() << "compute_Bytes_Received: " << bytes_received << std::endl; + dc.cout() << "compute_Calls_Received: " << calls_received << std::endl; graphlab::mpi_tools::finalize(); return EXIT_SUCCESS; From dd4b1525b43cd7ad07c7f833345fa98585b38cc2 Mon Sep 17 00:00:00 2001 From: sjx Date: Mon, 2 Dec 2013 14:35:23 +0800 Subject: [PATCH 25/50] support data affinity --- apps/example/CMakeLists.txt | 3 + apps/example/word_search.cpp | 422 +++++++++++++++++++++++ src/graphlab/graph/distributed_graph.hpp | 31 +- 3 files changed, 454 insertions(+), 2 deletions(-) create mode 100644 apps/example/word_search.cpp diff --git a/apps/example/CMakeLists.txt b/apps/example/CMakeLists.txt index 5a13bda9b6..481c208165 100644 --- a/apps/example/CMakeLists.txt +++ b/apps/example/CMakeLists.txt @@ -1,2 +1,5 @@ project(example) add_graphlab_executable(hello_world hello_world.cpp) + +project(word_search) +add_graphlab_executable(word_search word_search.cpp) diff --git a/apps/example/word_search.cpp b/apps/example/word_search.cpp new file mode 100644 index 0000000000..a035be7285 --- /dev/null +++ b/apps/example/word_search.cpp @@ -0,0 +1,422 @@ +/* + * Copyright (c) 2009 Carnegie Mellon University. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * For more about this software visit: + * + * http://www.graphlab.ml.cmu.edu + * + */ + +#include +#include +#include + +#include +// #include + +// Global random reset probability +double RESET_PROB = 0.15; + +double TOLERANCE = 1.0E-2; + +size_t ITERATIONS = 0; + +bool USE_DELTA_CACHE = false; + +// The vertex data is just the pagerank value (a double) +//typedef double vertex_data_type; + + + +//struct vertex_t : public graphlab::IS_POD_TYPE { +struct vertex_t { + int count; + bool is_doc; // + //std::string str; + std::vector str_vec; + + + vertex_t(std::vector& vec) : + count(0){str_vec=vec;is_doc=true;} + + vertex_t() : + count(0){is_doc=false;} + + void save(graphlab::oarchive& arc) const { + arc << count <> count >> str_vec >>is_doc; + } + +}; // end of edge data + +typedef vertex_t vertex_data_type; + +// There is no edge data in the pagerank application +typedef graphlab::empty edge_data_type; + +// The graph type is determined by the vertex and edge data types +typedef graphlab::distributed_graph graph_type; + + +inline graph_type::vertex_type +get_other_vertex(graph_type::edge_type& edge, + const graph_type::vertex_type& vertex) { + return vertex.id() == edge.source().id()? edge.target() : edge.source(); +}; // end of get_other_vertex + +/* + * A simple function used by graph.transform_vertices(init_vertex); + * to initialize the vertes data. + */ + + + +void init_vertex(graph_type::vertex_type& vertex) { vertex.data().count = 0; } + + +inline bool graph_loader(graph_type& graph, + const std::string& filename, + const std::string& line) { + ASSERT_FALSE(line.empty()); + namespace qi = boost::spirit::qi; + namespace ascii = boost::spirit::ascii; + namespace phoenix = boost::phoenix; + + graph_type::vertex_id_type source_id(-1), target_id(-1); + std::vector str_vec; + + if(boost::ends_with(filename,".edge")){ + const bool success = qi::phrase_parse + (line.begin(), line.end(), + // Begin grammar + ( + qi::ulong_[phoenix::ref(source_id) = qi::_1] >> -qi::char_(',') >> + qi::ulong_[phoenix::ref(target_id) = qi::_1] + ) + , + // End grammar + ascii::space); + + if(!success) return false; + graph.add_edge(source_id, target_id); + return true; // successful load + } + + + const bool success = qi::parse + (line.begin(), line.end(), + // Begin grammar + ( + qi::omit[qi::ulong_[phoenix::ref(source_id) = qi::_1] ]>> qi::omit[+qi::space] >> //-qi::char_(',') >> + +qi::alnum % qi::omit[+qi::space] + ), + str_vec); + + + if(!success) return false; + vertex_data_type v_data(str_vec); +// v_data.count=v_data.str_vec.size(); +// graph.add_edge(source_id, target_id, edge_data(obs, role)); + graph.add_vertex(source_id, v_data); + return true; // successful load +} // end of graph_loader + + +/* + * The factorized page rank update function extends ivertex_program + * specifying the: + * + * 1) graph_type + * 2) gather_type: double (returned by the gather function). Note + * that the gather type is not strictly needed here since it is + * assumed to be the same as the vertex_data_type unless + * otherwise specified + * + * In addition ivertex program also takes a message type which is + * assumed to be empty. Since we do not need messages no message type + * is provided. + * + * pagerank also extends graphlab::IS_POD_TYPE (is plain old data type) + * which tells graphlab that the pagerank program can be serialized + * (converted to a byte stream) by directly reading its in memory + * representation. If a vertex program does not exted + * graphlab::IS_POD_TYPE it must implement load and save functions. + */ +//graphlab::ivertex_program< Graph, GatherType, MessageType > + +class pagerank : + + public graphlab::ivertex_program { + + double last_change; +public: + + /** + * Gather only in edges. + */ +/* + void init(icontext_type& context, const vertex_type& vertex,const message_type& message) { + + //if(!vertex.data().is_doc) + context.signal(vertex); + } +*/ + edge_dir_type gather_edges(icontext_type& context, + const vertex_type& vertex) const { + if(vertex.data().is_doc) + return graphlab::NO_EDGES; + else + return graphlab::ALL_EDGES; + } // end of Gather edges + + + /* Gather the weighted rank of the adjacent page */ + int gather(icontext_type& context, const vertex_type& vertex, + edge_type& edge) const { + if(vertex.data().is_doc) + return 0; + else{ + int count=0; + vertex_type doc=get_other_vertex(edge,vertex); + for(int i=0;i> last_change; + } + +}; // end of factorized_pagerank update functor + + + + +int map_rank(const graph_type::vertex_type& v) { return v.data().count; } + +edge_data_type signal_target(graphlab::omni_engine::icontext_type& context, + graph_type::edge_type edge) { + context.signal(edge.target()); + return graphlab::empty(); +} + + + +int pagerank_sum(graph_type::vertex_type v) { + return v.data().count; +} + +int main(int argc, char** argv) { + // Initialize control plain using mpi + graphlab::mpi_tools::init(argc, argv); + graphlab::distributed_control dc; + global_logger().set_log_level(LOG_INFO); + + // Parse command line options ----------------------------------------------- + graphlab::command_line_options clopts("PageRank algorithm."); + std::string graph_dir; + std::string format = "adj"; + std::string exec_type = "synchronous"; + clopts.attach_option("graph", graph_dir, + "The graph file. If none is provided " + "then a toy graph will be created"); + clopts.add_positional("graph"); + clopts.attach_option("engine", exec_type, + "The engine type synchronous or asynchronous"); + clopts.attach_option("tol", TOLERANCE, + "The permissible change at convergence."); + clopts.attach_option("format", format, + "The graph file format"); + size_t powerlaw = 0; + clopts.attach_option("powerlaw", powerlaw, + "Generate a synthetic powerlaw out-degree graph. "); + clopts.attach_option("iterations", ITERATIONS, + "If set, will force the use of the synchronous engine" + "overriding any engine option set by the --engine parameter. " + "Runs complete (non-dynamic) PageRank for a fixed " + "number of iterations. Also overrides the iterations " + "option in the engine"); + clopts.attach_option("use_delta", USE_DELTA_CACHE, + "Use the delta cache to reduce time in gather."); + std::string saveprefix; + clopts.attach_option("saveprefix", saveprefix, + "If set, will save the resultant pagerank to a " + "sequence of files with prefix saveprefix"); + + if(!clopts.parse(argc, argv)) { + dc.cout() << "Error in parsing command line arguments." << std::endl; + return EXIT_FAILURE; + } + + + // Enable gather caching in the engine + clopts.get_engine_args().set_option("use_cache", USE_DELTA_CACHE); + + if (ITERATIONS) { + // make sure this is the synchronous engine + dc.cout() << "--iterations set. Forcing Synchronous engine, and running " + << "for " << ITERATIONS << " iterations." << std::endl; + //clopts.get_engine_args().set_option("type", "synchronous"); + clopts.get_engine_args().set_option("max_iterations", ITERATIONS); + clopts.get_engine_args().set_option("sched_allv", true); + } + + // Build the graph ---------------------------------------------------------- + dc.cout() << "Loading graph." << std::endl; + graphlab::timer timer; + graph_type graph(dc, clopts); + if(powerlaw > 0) { // make a synthetic graph + dc.cout() << "Loading synthetic Powerlaw graph." << std::endl; + graph.load_synthetic_powerlaw(powerlaw, false, 2.1, 100000000); + } + else if (graph_dir.length() > 0) { // Load the graph from a file + + dc.cout() << "Loading graph in format: "<< format << std::endl; + graph.load(graph_dir, graph_loader); +// graph.load_format(graph_dir, format); + } + else { + dc.cout() << "graph or powerlaw option must be specified" << std::endl; + clopts.print_description(); + return 0; + } + dc.cout() << "Loading graph. Finished in " + << timer.current_time() << std::endl; + + size_t bytes_sent=dc.bytes_sent(); + size_t calls_sent=dc.calls_sent(); + size_t network_bytes_sent=dc.network_bytes_sent(); + size_t bytes_received=dc.bytes_received(); + size_t calls_received=dc.calls_received(); + dc.cout() << "load_Bytes_Sent: " << bytes_sent << std::endl; + dc.cout() << "load_Calls_Sent: " << calls_sent << std::endl; + dc.cout() << "load_Network_Sent: " << network_bytes_sent<< std::endl; + dc.cout() << "load_Bytes_Received: " << bytes_received << std::endl; + dc.cout() << "load_Calls_Received: " << calls_received << std::endl; + + // must call finalize before querying the graph + dc.cout() << "Finalizing graph." << std::endl; + timer.start(); + graph.finalize(); + dc.cout() << "Finalizing graph. Finished in " + << timer.current_time() << std::endl; + + bytes_sent=dc.bytes_sent()-bytes_sent; + calls_sent=dc.calls_sent()-calls_sent; + network_bytes_sent=dc.network_bytes_sent()-network_bytes_sent; + bytes_received=dc.bytes_received()-bytes_received; + calls_received=dc.calls_received()-calls_received; + dc.cout() << "finalize_Bytes_Sent: " << bytes_sent << std::endl; + dc.cout() << "finalize_Calls_Sent: " << calls_sent << std::endl; + dc.cout() << "finalize_Network_Sent: " << network_bytes_sent<< std::endl; + dc.cout() << "finalize_Bytes_Received: " << bytes_received << std::endl; + dc.cout() << "finalize_Calls_Received: " << calls_received << std::endl; + + + dc.cout() << "#vertices: " << graph.num_vertices() + << " #edges:" << graph.num_edges() << std::endl; + + // Initialize the vertex data + //graph.transform_vertices(init_vertex); + + // Running The Engine ------------------------------------------------------- + graphlab::omni_engine engine(dc, graph, exec_type, clopts); + + + engine.map_reduce_edges(signal_target); + + bytes_sent=dc.bytes_sent()-bytes_sent; + calls_sent=dc.calls_sent()-calls_sent; + network_bytes_sent=dc.network_bytes_sent()-network_bytes_sent; + bytes_received=dc.bytes_received()-bytes_received; + calls_received=dc.calls_received()-calls_received; + dc.cout() << "before_start_Bytes_Sent: " << bytes_sent << std::endl; + dc.cout() << "before_start_Calls_Sent: " << calls_sent << std::endl; + dc.cout() << "before_start_Network_Sent: " << network_bytes_sent<< std::endl; + dc.cout() << "before_start_Bytes_Received: " << bytes_received << std::endl; + dc.cout() << "before_start_Calls_Received: " << calls_received << std::endl; + + //engine.signal_all(); + timer.start(); + engine.start(); + const double runtime = timer.current_time(); + dc.cout() << "----------------------------------------------------------" + << std::endl + << "Final Runtime (seconds): " << runtime + << std::endl + << "Updates executed: " << engine.num_updates() << std::endl + << "Update Rate (updates/second): " + << engine.num_updates() / runtime << std::endl; + + const int total_rank = graph.map_reduce_vertices(map_rank); + std::cout << "Total rank: " << total_rank << std::endl; + + // Save the final graph ----------------------------------------------------- + + bytes_sent=dc.bytes_sent()-bytes_sent; + calls_sent=dc.calls_sent()-calls_sent; + network_bytes_sent=dc.network_bytes_sent()-network_bytes_sent; + bytes_received=dc.bytes_received()-bytes_received; + calls_received=dc.calls_received()-calls_received; + dc.cout() << "compute_Bytes_Sent: " << bytes_sent << std::endl; + dc.cout() << "compute_Calls_Sent: " << calls_sent << std::endl; + dc.cout() << "compute_Network_Sent: " << network_bytes_sent<< std::endl; + dc.cout() << "compute_Bytes_Received: " << bytes_received << std::endl; + dc.cout() << "compute_Calls_Received: " << calls_received << std::endl; + + // Tear-down communication layer and quit ----------------------------------- + graphlab::mpi_tools::finalize(); + return EXIT_SUCCESS; +} // End of main + + +// We render this entire program in the documentation + + diff --git a/src/graphlab/graph/distributed_graph.hpp b/src/graphlab/graph/distributed_graph.hpp index 0706e3f998..953e5aa6dd 100644 --- a/src/graphlab/graph/distributed_graph.hpp +++ b/src/graphlab/graph/distributed_graph.hpp @@ -422,7 +422,7 @@ namespace graphlab { HIGH_MIRROR, LOW_MIRROR, NUM_ZONE_TYPES}; enum cuts_type {VERTEX_CUTS = 0, EDGE_CUTS, HYBRID_CUTS, HYBRID_GINGER_CUTS, - ZONE_CUTS, NUM_CUTS_TYPES}; + ZONE_CUTS, AFFINITY_CUTS,NUM_CUTS_TYPES}; struct vertex_type; typedef bool edge_list_type; @@ -2263,7 +2263,33 @@ namespace graphlab { logstream(LOG_WARNING) << "No files found matching " << original_path << std::endl; } - if (get_cuts_type() == HYBRID_GINGER_CUTS) { + if (get_cuts_type() == AFFINITY_CUTS) { +#ifdef _OPENMP +#pragma omp parallel for +#endif + for(size_t i = 0; i < graph_files.size(); ++i) { + if (true) { + logstream(LOG_EMPH) << "Loading graph from file: " << graph_files[i] << std::endl; + // is it a gzip file ? + const bool gzip = boost::ends_with(graph_files[i], ".gz"); + // open the stream + std::ifstream in_file(graph_files[i].c_str(), + std::ios_base::in | std::ios_base::binary); + // attach gzip if the file is gzip + boost::iostreams::filtering_stream fin; + // Using gzip filter + if (gzip) fin.push(boost::iostreams::gzip_decompressor()); + fin.push(in_file); + const bool success = load_from_stream(graph_files[i], fin, line_parser); + if(!success) { + logstream(LOG_FATAL) + << "\n\tError parsing file: " << graph_files[i] << std::endl; + } + fin.pop(); + if (gzip) fin.pop(); + } + } + } else if (get_cuts_type() == HYBRID_GINGER_CUTS) { for(size_t i = 0; i < graph_files.size(); ++i) { if ((parallel_ingress && (i % rpc.numprocs() == rpc.procid())) || (!parallel_ingress && (rpc.procid() == 0))) { @@ -3328,6 +3354,7 @@ namespace graphlab { else{ if (rpc.procid() == 0) logstream(LOG_EMPH) << "Use bipartite ingress with affinity" << std::endl; ingress_ptr = new distributed_bipartite_affinity_ingress(rpc.dc(), *this, direction); + set_cuts_type(AFFINITY_CUTS); } } else if (method == "bipartite_opt") { if (rpc.procid() == 0) logstream(LOG_EMPH) << "Use bipartite_opt ingress" << std::endl; From 9696a328c4d0cc6d3d1a4f0c8b3ed968d124ae11 Mon Sep 17 00:00:00 2001 From: sjx Date: Wed, 4 Dec 2013 14:59:30 +0800 Subject: [PATCH 26/50] refine code about bipartite ingress --- src/graphlab/graph/distributed_graph.hpp | 6 +- ...distributed_bipartite_affinity_ingress.hpp | 6 +- ... => distributed_bipartite_opt_ingress.hpp} | 119 +++++++++--------- 3 files changed, 63 insertions(+), 68 deletions(-) rename src/graphlab/graph/ingress/{distributed_bipartite_hybrid_ingress.hpp => distributed_bipartite_opt_ingress.hpp} (58%) diff --git a/src/graphlab/graph/distributed_graph.hpp b/src/graphlab/graph/distributed_graph.hpp index 953e5aa6dd..8239d3108c 100644 --- a/src/graphlab/graph/distributed_graph.hpp +++ b/src/graphlab/graph/distributed_graph.hpp @@ -82,7 +82,7 @@ #include #include -#include +#include #include #include @@ -408,7 +408,7 @@ namespace graphlab { friend class distributed_identity_ingress; friend class distributed_oblivious_ingress; friend class distributed_constrained_random_ingress; - friend class distributed_bipartite_hybrid_ingress; + friend class distributed_bipartite_opt_ingress; friend class distributed_bipartite_hash_ingress; friend class distributed_bipartite_affinity_ingress; friend class distributed_hybrid_ingress; @@ -3358,7 +3358,7 @@ namespace graphlab { } } else if (method == "bipartite_opt") { if (rpc.procid() == 0) logstream(LOG_EMPH) << "Use bipartite_opt ingress" << std::endl; - ingress_ptr = new distributed_bipartite_hybrid_ingress(rpc.dc(), *this, direction); + ingress_ptr = new distributed_bipartite_opt_ingress(rpc.dc(), *this, direction); } else if (method == "hybrid") { if (rpc.procid() == 0) logstream(LOG_EMPH) << "Use hybrid ingress" << std::endl; ingress_ptr = new distributed_hybrid_ingress(rpc.dc(), *this, threshold); diff --git a/src/graphlab/graph/ingress/distributed_bipartite_affinity_ingress.hpp b/src/graphlab/graph/ingress/distributed_bipartite_affinity_ingress.hpp index 9c778f2749..37e4934979 100644 --- a/src/graphlab/graph/ingress/distributed_bipartite_affinity_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_bipartite_affinity_ingress.hpp @@ -24,7 +24,7 @@ namespace graphlab { class distributed_graph; /** - * \brief Ingress object assigning edges using randoming hash function. + * \brief Ingress object with data affinity. */ template class distributed_bipartite_affinity_ingress : @@ -114,7 +114,7 @@ namespace graphlab { return; } } - + // directly add the vertices loaded from local file to the local graph graph.lvid2record.resize(bipartite_vertexs.size()); graph.local_graph.resize(bipartite_vertexs.size()); lvid_type lvid = 0; @@ -127,6 +127,7 @@ namespace graphlab { lvid++; } + // use master_exchange to exchange the mapping table for (typename master_hash_table_type::iterator it = mht.begin() ; it != mht.end(); ++it) { for (procid_t i = 0; i < bipartite_rpc.numprocs(); ++i) { if (i != bipartite_rpc.procid()) @@ -143,6 +144,7 @@ namespace graphlab { } master_exchange.clear(); + // resend all the edges foreach(const edge_buffer_record& rec,bipartite_edges){ if(source_is_special){ if(mht.find(rec.source)==mht.end()) diff --git a/src/graphlab/graph/ingress/distributed_bipartite_hybrid_ingress.hpp b/src/graphlab/graph/ingress/distributed_bipartite_opt_ingress.hpp similarity index 58% rename from src/graphlab/graph/ingress/distributed_bipartite_hybrid_ingress.hpp rename to src/graphlab/graph/ingress/distributed_bipartite_opt_ingress.hpp index 5b3a7abbbf..0471f4e437 100644 --- a/src/graphlab/graph/ingress/distributed_bipartite_hybrid_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_bipartite_opt_ingress.hpp @@ -1,7 +1,7 @@ -#ifndef GRAPHLAB_DISTRIBUTED_BIPARTITE_HYBRID_INGRESS_HPP -#define GRAPHLAB_DISTRIBUTED_BIPARTITE_HYBRID_INGRESS_HPP +#ifndef GRAPHLAB_DISTRIBUTED_BIPARTITE_OPT_INGRESS_HPP +#define GRAPHLAB_DISTRIBUTED_BIPARTITE_OPT_INGRESS_HPP #include @@ -23,10 +23,10 @@ namespace graphlab { class distributed_graph; /** - * \brief Ingress object assigning edges using randoming hash function. + * \brief Ingress object benefit for bipartite graph. */ template - class distributed_bipartite_hybrid_ingress : + class distributed_bipartite_opt_ingress : public distributed_ingress_base { public: typedef distributed_graph graph_type; @@ -51,22 +51,22 @@ namespace graphlab { graph_type& graph; - dc_dist_object hybrid_rpc; + dc_dist_object bipartite_rpc; - buffered_exchange hybrid_vertex_exchange; - buffered_exchange hybrid_edge_exchange; + buffered_exchange bipartite_vertex_exchange; + buffered_exchange bipartite_edge_exchange; - std::vector hybrid_edges; + std::vector bipartite_edges; bool source_is_special; public: - distributed_bipartite_hybrid_ingress(distributed_control& dc, graph_type& graph,const std::string& specialvertex) : - base_type(dc, graph),graph(graph),hybrid_rpc(dc, this), - hybrid_vertex_exchange(dc),hybrid_edge_exchange(dc) + distributed_bipartite_opt_ingress(distributed_control& dc, graph_type& graph,const std::string& specialvertex) : + base_type(dc, graph),graph(graph),bipartite_rpc(dc, this), + bipartite_vertex_exchange(dc),bipartite_edge_exchange(dc) { if(specialvertex=="source") @@ -76,31 +76,31 @@ namespace graphlab { } // end of constructor - ~distributed_bipartite_hybrid_ingress() { + ~distributed_bipartite_opt_ingress() { } - /** Add an edge to the ingress object using random assignment. */ + /** Add an edge to the ingress object to the favorite subset. */ void add_edge(vertex_id_type source, vertex_id_type target, const EdgeData& edata) { const edge_buffer_record record(source, target, edata); if(source_is_special){ const procid_t owning_proc = - graph_hash::hash_vertex(source) % hybrid_rpc.numprocs(); - hybrid_edge_exchange.send(owning_proc, record); + graph_hash::hash_vertex(source) % bipartite_rpc.numprocs(); + bipartite_edge_exchange.send(owning_proc, record); } else{ const procid_t owning_proc = - graph_hash::hash_vertex(target) % hybrid_rpc.numprocs(); - hybrid_edge_exchange.send(owning_proc, record); + graph_hash::hash_vertex(target) % bipartite_rpc.numprocs(); + bipartite_edge_exchange.send(owning_proc, record); } } // end of add edge void add_vertex(vertex_id_type vid, const VertexData& vdata) { const vertex_buffer_record record(vid, vdata); const procid_t owning_proc = - graph_hash::hash_vertex(vid) % hybrid_rpc.numprocs(); - hybrid_vertex_exchange.send(owning_proc, record); + graph_hash::hash_vertex(vid) % bipartite_rpc.numprocs(); + bipartite_vertex_exchange.send(owning_proc, record); } // end of add vertex void finalize() { @@ -110,80 +110,73 @@ namespace graphlab { procid_t proc; - hybrid_edge_exchange.flush(); - hybrid_vertex_exchange.flush(); - //std::map > count_map; + bipartite_edge_exchange.flush(); + bipartite_vertex_exchange.flush(); boost::unordered_map > count_map; - //std::map owner_map; boost::unordered_map owner_map; std::set master_set; std::vector proc_num_edges; - { - size_t changed_size = hybrid_edge_exchange.size() + hybrid_vertex_exchange.size(); - hybrid_rpc.all_reduce(changed_size); + { //skipping finalization when no changes hapened + size_t changed_size = bipartite_edge_exchange.size() + bipartite_vertex_exchange.size(); + bipartite_rpc.all_reduce(changed_size); if (changed_size == 0) { logstream(LOG_INFO) << "Skipping Graph Finalization because no changes happened..." << std::endl; return; } } - proc_num_edges.resize(hybrid_rpc.numprocs()); + // recv the edges + // count_map is used to calculate the distribution of a favorite vertice 's neighbors + // proc_num_edges contains the number of edges in all machines sent from this machine. + proc_num_edges.resize(bipartite_rpc.numprocs()); proc = -1; - while(hybrid_edge_exchange.recv(proc, edge_buffer)) { + while(bipartite_edge_exchange.recv(proc, edge_buffer)) { foreach(const edge_buffer_record& rec, edge_buffer) { - hybrid_edges.push_back(rec); + bipartite_edges.push_back(rec); if(source_is_special){ if(count_map.find(rec.source)==count_map.end()){ - count_map[rec.source].resize(hybrid_rpc.numprocs()); - //owner_map[rec.source]=hybrid_rpc.procid(); + count_map[rec.source].resize(bipartite_rpc.numprocs()); } const procid_t target_proc = - graph_hash::hash_vertex(rec.target) % hybrid_rpc.numprocs(); - count_map[rec.source][target_proc]+=1;///(msg.second+1); - //const procid_t max_proc = owner_map[rec.source]; - //if(count_map[rec.source][target_proc] > count_map[rec.source][max_proc]) - //owner_map[rec.source]=target_proc; + graph_hash::hash_vertex(rec.target) % bipartite_rpc.numprocs(); + count_map[rec.source][target_proc]+=1; } else { if(count_map.find(rec.target)==count_map.end()){ - count_map[rec.target].resize(hybrid_rpc.numprocs()); - //owner_map[rec.target]=hybrid_rpc.procid(); + count_map[rec.target].resize(bipartite_rpc.numprocs()); } const procid_t source_proc = - graph_hash::hash_vertex(rec.source) % hybrid_rpc.numprocs(); - count_map[rec.target][source_proc]+=1;///(msg.second+1); - //const procid_t max_proc = owner_map[rec.target]; - //if(count_map[rec.target][source_proc] > count_map[rec.target][max_proc]) - // owner_map[rec.target]=source_proc; + graph_hash::hash_vertex(rec.source) % bipartite_rpc.numprocs(); + count_map[rec.target][source_proc]+=1; } } } - hybrid_edge_exchange.clear(); + bipartite_edge_exchange.clear(); + // a loop to calculate where should each favorite vertice go. + // and tell that machine with vid_buffer . proc = -1; - buffered_exchange vid_buffer(hybrid_rpc.dc()); - //for (typename boost::unordered_map::iterator it = owner_map.begin(); - // it != owner_map.end(); ++it){ - // vid_buffer.send(it->second, it->first); + buffered_exchange vid_buffer(bipartite_rpc.dc()); for (typename boost::unordered_map >::iterator it = count_map.begin(); it != count_map.end(); ++it){ - procid_t maxproc=hybrid_rpc.procid(); + procid_t maxproc=bipartite_rpc.procid(); double maxscore =(it->second)[maxproc]-sqrt(1.0*proc_num_edges[maxproc]); - for(size_t i=0; i < hybrid_rpc.numprocs();i++){ + for(size_t i=0; i < bipartite_rpc.numprocs();i++){ double current_score=(it->second)[i]-sqrt(1.0*proc_num_edges[i]); if(current_score > maxscore){ maxscore =current_score; maxproc =i; } } - for(size_t i=0; i < hybrid_rpc.numprocs();i++){ + for(size_t i=0; i < bipartite_rpc.numprocs();i++){ proc_num_edges[maxproc]+=(it->second)[i]; } owner_map[it->first]=maxproc; vid_buffer.send(maxproc, it->first); } + // find all the favorite vertices this machine own vid_buffer.flush(); { typename buffered_exchange::buffer_type buffer; @@ -199,10 +192,9 @@ namespace graphlab { } vid_buffer.clear(); - - // re-send edges - for (size_t i = 0; i < hybrid_edges.size(); i++) { - edge_buffer_record& rec = hybrid_edges[i]; + // send the all the edges out using base_type::edge_exchange + for (size_t i = 0; i < bipartite_edges.size(); i++) { + edge_buffer_record& rec = bipartite_edges[i]; procid_t owner_proc ; if(source_is_special) owner_proc = owner_map[rec.source]; @@ -211,37 +203,38 @@ namespace graphlab { base_type::edge_exchange.send(owner_proc,rec); } + // add the favorite vertices to the local graph. graph.lvid2record.resize(master_set.size()); graph.local_graph.resize(master_set.size()); lvid_type lvid = 0; foreach(const vertex_id_type& vid,master_set){ graph.vid2lvid[vid]=lvid; - //graph.local_graph.add_vertex(lvid,vdata()); vertex_record& vrec = graph.lvid2record[lvid]; vrec.gvid = vid; - vrec.owner=hybrid_rpc.procid(); + vrec.owner=bipartite_rpc.procid(); lvid++; } - { // Receive any vertex data sent by other machines + { // resend all vertex data . vertex_buffer_type vertex_buffer; procid_t sending_proc(-1); - while(hybrid_vertex_exchange.recv(sending_proc, vertex_buffer)) { + while(bipartite_vertex_exchange.recv(sending_proc, vertex_buffer)) { foreach(const vertex_buffer_record& rec, vertex_buffer) { if(owner_map.find(rec.vid) !=owner_map.end()){ base_type::vertex_exchange.send(owner_map[rec.vid],rec); } else{ - base_type::vertex_exchange.send(hybrid_rpc.procid(),rec); + base_type::vertex_exchange.send(bipartite_rpc.procid(),rec); } } } - hybrid_vertex_exchange.clear(); - } // end of loop to populate vrecmap + bipartite_vertex_exchange.clear(); + } + // reuse the finalize of base_type base_type::finalize(); } // end of finalize - }; // end of distributed_bipartite_hybrid_ingress + }; // end of distributed_bipartite_opt_ingress }; // end of namespace graphlab #include From 9335ef1b6eaad775300fe79a068225eca5b91669 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Fri, 13 Dec 2013 14:44:30 +0800 Subject: [PATCH 27/50] miss files --- .../distributed_bipartite_aweto_ingress.hpp | 347 ++++++++++++++++++ .../distributed_bipartite_random_ingress.hpp | 78 ++++ 2 files changed, 425 insertions(+) create mode 100644 src/graphlab/graph/ingress/distributed_bipartite_aweto_ingress.hpp create mode 100644 src/graphlab/graph/ingress/distributed_bipartite_random_ingress.hpp diff --git a/src/graphlab/graph/ingress/distributed_bipartite_aweto_ingress.hpp b/src/graphlab/graph/ingress/distributed_bipartite_aweto_ingress.hpp new file mode 100644 index 0000000000..417f473508 --- /dev/null +++ b/src/graphlab/graph/ingress/distributed_bipartite_aweto_ingress.hpp @@ -0,0 +1,347 @@ +/** + * Copyright (c) 2013 Institute of Parallel and Distributed Systems, Shanghai Jiao Tong University. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * For more about this software visit: + * + * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html + * + */ + + +#ifndef GRAPHLAB_DISTRIBUTED_BIPARTITE_AWETO_INGRESS_HPP +#define GRAPHLAB_DISTRIBUTED_BIPARTITE_AWETO_INGRESS_HPP + +#include + +#include +#include +#include +#include +#include +#include + + +#include +#include +#include +#include + +#define TUNING +namespace graphlab { + template + class distributed_graph; + + /** + * \brief Ingress object benefit for bipartite graph. + */ + template + class distributed_bipartite_aweto_ingress : + public distributed_ingress_base { + public: + typedef distributed_graph graph_type; + /// The type of the vertex data stored in the graph + typedef VertexData vertex_data_type; + /// The type of the edge data stored in the graph + typedef EdgeData edge_data_type; + + + typedef distributed_ingress_base base_type; + + typedef typename graph_type::vertex_record vertex_record; + + typedef typename base_type::edge_buffer_record edge_buffer_record; + typedef typename buffered_exchange::buffer_type + edge_buffer_type; + + typedef typename base_type::vertex_buffer_record vertex_buffer_record; + typedef typename buffered_exchange::buffer_type + vertex_buffer_type; + + + /// The rpc interface for this object + dc_dist_object bipartite_rpc; + /// The underlying distributed graph object that is being loaded + graph_type& graph; + + std::vector bipartite_edges; + + bool favorite_source; + + /* ingress exchange */ + buffered_exchange bipartite_vertex_exchange; + buffered_exchange bipartite_edge_exchange; + + public: + distributed_bipartite_aweto_ingress(distributed_control& dc, graph_type& graph, const std::string& favorite) : + base_type(dc, graph), bipartite_rpc(dc, this), graph(graph), +#ifdef _OPENMP + bipartite_vertex_exchange(dc, omp_get_max_threads()), + bipartite_edge_exchange(dc, omp_get_max_threads()) +#else + bipartite_vertex_exchange(dc),bipartite_edge_exchange(dc) +#endif + { + favorite_source = (favorite == "source") ? true : false; + } // end of constructor + + ~distributed_bipartite_aweto_ingress() { + + } + + /** accumulate edges temporal rally point using random of "favorite" assignment. */ + void add_edge(vertex_id_type source, vertex_id_type target, + const EdgeData& edata) { + vertex_id_type favorite = favorite_source ? source : target; + const procid_t owning_proc = + graph_hash::hash_vertex(favorite) % bipartite_rpc.numprocs(); + const edge_buffer_record record(source, target, edata); +#ifdef _OPENMP + bipartite_edge_exchange.send(owning_proc, record, omp_get_thread_num()); +#else + bipartite_edge_exchange.send(owning_proc, record); +#endif + } // end of add edge + + /** accumulate edges temporal rally point using random of "favorite" assignment. */ + void add_vertex(vertex_id_type vid, const VertexData& vdata) { + const procid_t owning_proc = + graph_hash::hash_vertex(vid) % bipartite_rpc.numprocs(); + const vertex_buffer_record record(vid, vdata); +#ifdef _OPENMP + bipartite_vertex_exchange.send(owning_proc, record, omp_get_thread_num()); +#else + bipartite_vertex_exchange.send(owning_proc, record); +#endif + } // end of add vertex + + void finalize() { + graphlab::timer ti; + + size_t nprocs = bipartite_rpc.numprocs(); + procid_t l_procid = bipartite_rpc.procid(); + + + bipartite_rpc.full_barrier(); + + if (l_procid == 0) { + memory_info::log_usage("start finalizing"); + logstream(LOG_EMPH) << "bipartite aweto finalizing ..." + << " #verts=" << graph.local_graph.num_vertices() + << " #edges=" << graph.local_graph.num_edges() + << " favorite=" << (favorite_source ? "source" : "target") + << std::endl; + } + + /**************************************************************************/ + /* */ + /* Flush any additional data */ + /* */ + /**************************************************************************/ + bipartite_edge_exchange.flush(); bipartite_vertex_exchange.flush(); + + /** + * Fast pass for redundant finalization with no graph changes. + */ + { + size_t changed_size = bipartite_edge_exchange.size() + bipartite_vertex_exchange.size(); + bipartite_rpc.all_reduce(changed_size); + if (changed_size == 0) { + logstream(LOG_INFO) << "Skipping Graph Finalization because no changes happened..." << std::endl; + return; + } + } + + /**************************************************************************/ + /* */ + /* calculate the distribution of favorite vertex's neighbors */ + /* */ + /**************************************************************************/ + boost::unordered_map > count_map; + edge_buffer_type edge_buffer; + procid_t proc(-1); + while(bipartite_edge_exchange.recv(proc, edge_buffer)) { + foreach(const edge_buffer_record& rec, edge_buffer) { + vertex_id_type favorite, second; + if (favorite_source) { favorite = rec.source; second = rec.target; } + else { favorite = rec.target; second = rec.source; } + + if(count_map.find(favorite) == count_map.end()) + count_map[favorite].resize(nprocs); + + const procid_t owner_proc = graph_hash::hash_vertex(second) % nprocs; + count_map[favorite][owner_proc] += 1; + + bipartite_edges.push_back(rec); + } + } + bipartite_edge_exchange.clear(); + + + /**************************************************************************/ + /* */ + /* record heuristic location of favorite vertex */ + /* */ + /**************************************************************************/ + buffered_exchange vid_buffer(bipartite_rpc.dc()); + std::set own_vid_set; + + // record current nedges distributed from this machine. + std::vector proc_num_edges(nprocs); + boost::unordered_map mht; + + for(typename boost::unordered_map >::iterator it = count_map.begin(); + it != count_map.end(); ++it) { + procid_t best_proc = l_procid; + // heuristic score + double best_score = (it->second)[best_proc] + - sqrt(1.0*proc_num_edges[best_proc]); + + for(size_t i = 0; i < bipartite_rpc.numprocs(); i++) { + double score = (it->second)[i] + - sqrt(1.0*proc_num_edges[i]); + if(score > best_score) { + best_proc = i; + best_score = score; + } + } + + // update nedges + for(size_t i = 0; i < bipartite_rpc.numprocs(); i++) + proc_num_edges[best_proc] += (it->second)[i]; + + mht[it->first] = best_proc; + vid_buffer.send(best_proc, it->first); + } + + // find all favorite vertices this machine own + vid_buffer.flush(); + { + typename buffered_exchange::buffer_type buffer; + procid_t recvid(-1); + while(vid_buffer.recv(recvid, buffer)) { + foreach(const vertex_id_type vid, buffer) + own_vid_set.insert(vid); + } + } + vid_buffer.clear(); + +#ifdef TUNING + if(l_procid == 0) { + logstream(LOG_INFO) << "hold " << own_vid_set.size() << " masters: " + << ti.current_time() + << " secs" + << std::endl; + } +#endif + + + /**************************************************************************/ + /* */ + /* exchange edges */ + /* */ + /**************************************************************************/ + for (size_t i = 0; i < bipartite_edges.size(); i++) { + edge_buffer_record& rec = bipartite_edges[i]; + vertex_id_type favorite = favorite_source ? rec.source : rec.target; + procid_t owner_proc = mht[favorite]; + // save to the buffer of edge_exchange in ingress_base + base_type::edge_exchange.send(owner_proc,rec); + } + +#ifdef TUNING + if(l_procid == 0) { + logstream(LOG_INFO) << "exchange edges: " + << ti.current_time() + << " secs" + << std::endl; + } +#endif + + + /**************************************************************************/ + /* */ + /* add vertices to local graph */ + /* */ + /**************************************************************************/ + graph.lvid2record.resize(own_vid_set.size()); + graph.local_graph.resize(own_vid_set.size()); + lvid_type lvid = 0; + foreach(const vertex_id_type& vid, own_vid_set){ + graph.vid2lvid[vid] = lvid; + vertex_record& vrec = graph.lvid2record[lvid]; + vrec.gvid = vid; + vrec.owner = bipartite_rpc.procid(); + lvid++; + } + own_vid_set.clear(); + +#ifdef TUNING + if(l_procid == 0) { + logstream(LOG_INFO) << "add vertices to local graph: " + << ti.current_time() + << " secs" + << std::endl; + } +#endif + + + /**************************************************************************/ + /* */ + /* re-send favorite vertex data */ + /* */ + /**************************************************************************/ + { + vertex_buffer_type vertex_buffer; procid_t sending_proc(-1); + while(bipartite_vertex_exchange.recv(sending_proc, vertex_buffer)) { + foreach(const vertex_buffer_record& rec, vertex_buffer) { + if(mht.find(rec.vid) != mht.end()) { + base_type::vertex_exchange.send(mht[rec.vid], rec); + } else { + base_type::vertex_exchange.send(l_procid, rec); + } + } + } + bipartite_vertex_exchange.clear(); + mht.clear(); + } + +#ifdef TUNING + if(l_procid == 0) { + logstream(LOG_INFO) << "exchange vertex data: " + << ti.current_time() + << " secs" + << std::endl; + } +#endif + + + // call base finalize() + base_type::finalize(); + if(l_procid == 0) { + memory_info::log_usage("bipartite aweto finalizing graph done."); + logstream(LOG_EMPH) << "bipartite aweto finalizing graph. (" + << ti.current_time() + << " secs)" + << std::endl; + } + } // end of finalize + + }; // end of distributed_bipartite_aweto_ingress +}; // end of namespace graphlab +#include + + +#endif diff --git a/src/graphlab/graph/ingress/distributed_bipartite_random_ingress.hpp b/src/graphlab/graph/ingress/distributed_bipartite_random_ingress.hpp new file mode 100644 index 0000000000..5c0b8ee8ee --- /dev/null +++ b/src/graphlab/graph/ingress/distributed_bipartite_random_ingress.hpp @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2013 Institute of Parallel and Distributed Systems, Shanghai Jiao Tong University. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * For more about this software visit: + * + * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html + * + */ + +#ifndef GRAPHLAB_DISTRIBUTED_BIPARTITE_RANDOM_INGRESS_HPP +#define GRAPHLAB_DISTRIBUTED_BIPARTITE_RANDOM_INGRESS_HPP + +#include + +#include +#include +#include +#include + + +#include +namespace graphlab { + template + class distributed_graph; + + /** + * \brief Ingress object assigning edges using randoming hash function on favorite. + */ + template + class distributed_bipartite_random_ingress : + public distributed_ingress_base { + public: + typedef distributed_graph graph_type; + /// The type of the vertex data stored in the graph + typedef VertexData vertex_data_type; + /// The type of the edge data stored in the graph + typedef EdgeData edge_data_type; + + + typedef distributed_ingress_base base_type; + + bool favorite_source; + public: + distributed_bipartite_random_ingress(distributed_control& dc, graph_type& graph, const std::string& favorite) : + base_type(dc, graph) { + favorite_source = (favorite == "source") ? true : false; + } // end of constructor + + ~distributed_bipartite_random_ingress() { } + + /** Add an edge to the ingress object using random of "favorite" assignment. */ + void add_edge(vertex_id_type source, vertex_id_type target, + const EdgeData& edata) { + typedef typename base_type::edge_buffer_record edge_buffer_record; + vertex_id_type favorite = favorite_source ? source : target; + const procid_t owning_proc = graph_hash::hash_vertex(favorite) % base_type::rpc.numprocs(); + const edge_buffer_record record(source, target, edata); + base_type::edge_exchange.send(owning_proc, record); + } // end of add edge + }; // end of distributed_bipartite_random_ingress +}; // end of namespace graphlab +#include + + +#endif From 457af69f619beddbbc859678871be07835259959 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Sun, 29 Dec 2013 15:58:04 +0800 Subject: [PATCH 28/50] refine ginger for good balance --- src/graphlab/engine/powerlyra_sync_engine.hpp | 17 +- src/graphlab/engine/synchronous_engine.hpp | 13 +- .../distributed_hybrid_ginger_ingress.hpp | 284 +++++++++--------- .../ingress/distributed_hybrid_ingress.hpp | 2 +- .../ingress/distributed_ingress_base.hpp | 19 ++ .../graph/ingress/ingress_edge_decision.hpp | 44 +-- 6 files changed, 176 insertions(+), 203 deletions(-) diff --git a/src/graphlab/engine/powerlyra_sync_engine.hpp b/src/graphlab/engine/powerlyra_sync_engine.hpp index 46894a4dc9..19d446c139 100644 --- a/src/graphlab/engine/powerlyra_sync_engine.hpp +++ b/src/graphlab/engine/powerlyra_sync_engine.hpp @@ -1580,17 +1580,16 @@ namespace graphlab { #endif if (rmi.procid() == 0) { + logstream(LOG_INFO) << "Compute Balance: "; + for (size_t i = 0;i < all_compute_time_vec.size(); ++i) { + logstream(LOG_INFO) << all_compute_time_vec[i] << " "; + } #ifdef TUNING logstream(LOG_INFO) << "Total Calls(G|A|S): " << completed_gathers.value << "|" << completed_applys.value << "|" << completed_scatters.value << std::endl; -#endif - logstream(LOG_INFO) << "Compute Balance: "; - for (size_t i = 0;i < all_compute_time_vec.size(); ++i) { - logstream(LOG_INFO) << all_compute_time_vec[i] << " "; - } logstream(LOG_INFO) << std::endl; logstream(LOG_EMPH) << " Execution Time: " << exec_time << std::endl; logstream(LOG_EMPH) << "Breakdown(X|R|G|A|S): " @@ -1600,6 +1599,7 @@ namespace graphlab { << apply_time << "|" << scatter_time << std::endl; +#endif } rmi.full_barrier(); @@ -1722,11 +1722,10 @@ namespace graphlab { && ((edge_dirs[lvid] == graphlab::OUT_EDGES) || (edge_dirs[lvid] == graphlab::ALL_EDGES)))) { send_activs(lvid, thread_id); - ++vcount; } } } - if(vcount % TRY_RECV_MOD == 0) recv_activs(); + if(++vcount % TRY_RECV_MOD == 0) recv_activs(); } num_active_vertices += nactive_inc; activ_exchange.partial_flush(); @@ -1821,14 +1820,14 @@ namespace graphlab { } // If the accum contains a value for the gather - if (accum_is_set) { send_accum(lvid, accum, thread_id); ++vcount; } + if(accum_is_set) send_accum(lvid, accum, thread_id); if(!graph.l_is_master(lvid)) { // if this is not the master clear the vertex program vertex_programs[lvid] = vertex_program_type(); } // try to recv gathers if there are any in the buffer - if(vcount % TRY_RECV_MOD == 0) recv_accums(); + if(++vcount % TRY_RECV_MOD == 0) recv_accums(); } } // end of loop over vertices to compute gather accumulators completed_gathers += ngather_inc; diff --git a/src/graphlab/engine/synchronous_engine.hpp b/src/graphlab/engine/synchronous_engine.hpp index 8b43fdd058..260a27d803 100644 --- a/src/graphlab/engine/synchronous_engine.hpp +++ b/src/graphlab/engine/synchronous_engine.hpp @@ -56,7 +56,7 @@ #include -#undef TUNING +#define TUNING namespace graphlab { @@ -1513,17 +1513,16 @@ namespace graphlab { #endif if (rmi.procid() == 0) { + logstream(LOG_INFO) << "Compute Balance: "; + for (size_t i = 0;i < all_compute_time_vec.size(); ++i) { + logstream(LOG_INFO) << all_compute_time_vec[i] << " "; + } #ifdef TUNING logstream(LOG_INFO) << "Total Calls(G|A|S): " << completed_gathers.value << "|" << completed_applys.value << "|" << completed_scatters.value << std::endl; -#endif - logstream(LOG_INFO) << "Compute Balance: "; - for (size_t i = 0;i < all_compute_time_vec.size(); ++i) { - logstream(LOG_INFO) << all_compute_time_vec[i] << " "; - } logstream(LOG_INFO) << std::endl; logstream(LOG_EMPH) << " Execution Time: " << exec_time << std::endl; logstream(LOG_EMPH) << "Breakdown(X|R|G|A|S): " @@ -1533,7 +1532,7 @@ namespace graphlab { << apply_time << "|" << scatter_time << std::endl; - +#endif } rmi.full_barrier(); // Stop the aggregator diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp index be511dbe6a..7e0f29be1f 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp @@ -80,26 +80,12 @@ namespace graphlab { typedef typename buffered_exchange::buffer_type vertex_buffer_type; - struct batch_edge_buffer_record { - std::vector sources; - vertex_id_type target; - std::vector edatas; - - batch_edge_buffer_record( - const std::vector& sources = std::vector() , - const vertex_id_type& target = vertex_id_type(-1), - const std::vector& edatas = std::vector()) : - sources(sources), target(target), edatas(edatas) { } - - void load(iarchive& arc) { arc >> sources >> target >> edatas; } - void save(oarchive& arc) const { arc << sources << target << edatas; } - }; - typedef typename buffered_exchange::buffer_type - batch_edge_buffer_type; + typedef typename boost::unordered_map > raw_map_type; /// detail vertex record for the second pass coordination. typedef typename base_type::vertex_negotiator_record - vertex_negotiator_record; + vertex_negotiator_record; /// ginger structure /** Type of the master location hash table: [vertex-id, location-of-master] */ @@ -111,10 +97,9 @@ namespace graphlab { master_buffer_type; typedef typename std::pair - proc_edges_pair_type; - typedef typename buffered_exchange::buffer_type - proc_edges_buffer_type; - + proc_score_pair_type; + typedef typename buffered_exchange::buffer_type + proc_score_buffer_type; /// The rpc interface for this object dc_dist_object hybrid_rpc; @@ -136,19 +121,14 @@ namespace graphlab { buffered_exchange low_edge_exchange; buffered_exchange resend_vertex_exchange; - /** master hash table: - * mht is the synchronized one across the cluster, - * mht_incremental is the new added mht which stays local since the last sync. - */ - // consider both #edge and #vertex + /** master hash table (mht): location mapping of low-degree vertices */ master_hash_table_type mht; buffered_exchange mht_exchange; - master_hash_table_type mht_incr; - //these are used for edges balance + // consider both #edge and #vertex std::vector proc_balance; - buffered_exchange< proc_edges_pair_type > proc_edges_exchange; - std::vector proc_edges_incr; + std::vector proc_score_incr; + buffered_exchange< proc_score_pair_type > proc_score_exchange; /// heuristic model from fennel /// records about the number of edges and vertices in the graph @@ -178,8 +158,8 @@ namespace graphlab { hybrid_vertex_exchange(dc), #endif high_edge_exchange(dc), low_edge_exchange(dc), resend_vertex_exchange(dc), - mht_exchange(dc), proc_balance(dc.numprocs()), proc_edges_exchange(dc), - proc_edges_incr(dc.numprocs()), + mht_exchange(dc), proc_balance(dc.numprocs()), + proc_score_incr(dc.numprocs()), proc_score_exchange(dc), tot_nedges(tot_nedges), tot_nverts(tot_nverts), interval(interval) { ASSERT_GT(tot_nedges, 0); ASSERT_GT(tot_nverts, 0); @@ -222,27 +202,55 @@ namespace graphlab { #endif } // end of add vertex - bool is_sync() { return mht_incr.size() > interval; } - void sync_heurisitc_info() { + /* ginger heuristic for low-degree vertex */ + procid_t ginger_to_proc (const vertex_id_type target, + const std::vector& records) { + size_t nprocs = hybrid_rpc.numprocs(); + std::vector proc_score(nprocs); + std::vector proc_degrees(nprocs); + + for (size_t i = 0; i < records.size(); ++i) { + if (mht.find(records[i].source) != mht.end()) + proc_degrees[mht[records[i].source]]++; + } + + for (size_t i = 0; i < nprocs; ++i) { + proc_score[i] = proc_degrees[i] + - alpha * gamma * pow(proc_balance[i], (gamma - 1)); + } + + double best_score = proc_score[0]; + procid_t best_proc = 0; + for (size_t i = 1; i < nprocs; ++i) { + if (proc_score[i] > best_score) { + best_score = proc_score[i]; + best_proc = i; + } + } + + return best_proc; + }; + + /* ginger heuristic for low-degree vertex */ + void sync_heuristic() { size_t nprocs = hybrid_rpc.numprocs(); procid_t l_procid = hybrid_rpc.procid(); - /* send mht_incr */ - for (typename master_hash_table_type::iterator it = mht_incr.begin(); - it != mht_incr.end(); ++it) { - // broadcast - for (procid_t i = 0; i < nprocs; ++i) { - if (i != l_procid) - mht_exchange.send(i, master_pair_type(it->first, it->second)); - } - // update mht - mht[it->first] = it->second; - } - mht_incr.clear(); + // send proc_score_incr + for (procid_t p = 0; p < nprocs; p++) { + for (procid_t i = 0; i < nprocs; i++) + if (i != l_procid) + proc_score_exchange.send(i, std::make_pair(p, proc_score_incr[p])); + proc_score_incr[p] = 0; + } + + // flush proc_score_incr and mht but w/o spin + proc_score_exchange.partial_flush(0); mht_exchange.partial_flush(0); - /* try to receive mht_incr and update mht */ + + // update local mht and proc_balance master_buffer_type master_buffer; procid_t proc = -1; while(mht_exchange.recv(proc, master_buffer, false)) { @@ -251,88 +259,104 @@ namespace graphlab { } mht_exchange.clear(); - - /* send proc_edges_incr for edges balance */ - for (procid_t p = 0; p < nprocs; p++) { - if (p != l_procid) { - for (procid_t i = 0; i < nprocs; i++) - proc_edges_exchange.send(p, std::make_pair(i, proc_edges_incr[i])); - } - } - proc_edges_exchange.partial_flush(0); - - /* try to receive proc_edge_inc */ - proc_edges_buffer_type proc_edge_buffer; + proc_score_buffer_type proc_edge_buffer; proc = -1; - while (proc_edges_exchange.recv(proc, proc_edge_buffer, false)) { - foreach (const proc_edges_pair_type& pair, proc_edge_buffer) - proc_edges_incr[pair.first] += pair.second; - } - proc_edges_exchange.clear(); - - - /* roughly convert proc_nedges to proc_nverts for load balancing */ - for (procid_t i = 0; i < nprocs; i++) { - proc_balance[i] += - proc_edges_incr[i] * float(tot_nverts) / tot_nedges; - proc_edges_incr[i] = 0; + while (proc_score_exchange.recv(proc, proc_edge_buffer, false)) { + foreach (const proc_score_pair_type& pair, proc_edge_buffer) + proc_balance[pair.first] += pair.second; } + proc_score_exchange.clear(); } - void add_edges(std::vector& sources, vertex_id_type target, - const std::vector& edatas) { - size_t nprocs = hybrid_rpc.numprocs(); - procid_t owning_proc = 0; - - if (sources.size() > threshold) { - // TODO: no need send, just resend latter - owning_proc = graph_hash::hash_vertex(target) % nprocs; - for (size_t i = 0; i < sources.size(); ++i) { - edge_buffer_record record(sources[i], target, edatas[i]); - high_edge_exchange.send(owning_proc, record); - } - } else { - owning_proc = base_type::edge_decision.edge_to_proc_ginger( - sources, target, mht, mht_incr, proc_balance, alpha, gamma); - for (size_t i = 0; i < sources.size(); ++i) { - edge_buffer_record record(sources[i], target, edatas[i]); - low_edge_exchange.send(owning_proc, record); - } - - // update edge counter - proc_edges_incr[owning_proc] += sources.size(); - } - // update vertex mapping table - mht_incr[target] = owning_proc; - - // synchronize heurisitic info - if (is_sync()) sync_heurisitc_info(); - } // end of add edges - - void assign_hybrid_edges() { - typedef typename boost::unordered_map - batch_record_map_type; - batch_record_map_type batch_map; + graphlab::timer ti; + size_t nprocs = hybrid_rpc.numprocs(); + procid_t l_procid = hybrid_rpc.procid(); + raw_map_type raw_map; + size_t vcount = 0; + // collect edges edge_buffer_type edge_buffer; procid_t proc = -1; while (hybrid_edge_exchange.recv(proc, edge_buffer)) { foreach(const edge_buffer_record& rec, edge_buffer) { - batch_map[rec.target].sources.push_back(rec.source); - batch_map[rec.target].edatas.push_back(rec.edata); + raw_map[rec.target].push_back(rec); } } hybrid_edge_exchange.clear(); - if (hybrid_rpc.procid() == 0) - logstream(LOG_INFO) << "receive " << batch_map.size() - << " batch edges done." << std::endl; +#ifdef TUNING + if(l_procid == 0) { + logstream(LOG_INFO) << "collect raw map: " + << ti.current_time() + << " secs" + << std::endl; + } + logstream(LOG_INFO) << "receive " << raw_map.size() + << " vertices done." << std::endl; +#endif + + + //assign vertices and its in-edges to hosting node + for (typename raw_map_type::iterator it = raw_map.begin(); + it != raw_map.end(); ++it) { + vertex_id_type target = it->first; + procid_t owning_proc = 0; + size_t degree = it->second.size(); + + if (degree > threshold) { + // TODO: no need send, just resend latter + owning_proc = graph_hash::hash_vertex(target) % nprocs; + for (size_t i = 0; i < degree; ++i) + high_edge_exchange.send(owning_proc, it->second[i]); + } else { + owning_proc = ginger_to_proc(target, it->second); + for (size_t i = 0; i < degree; ++i) + low_edge_exchange.send(owning_proc, it->second[i]); + + // update mht and nedges_incr + for (procid_t p = 0; p < nprocs; ++p) { + if (p != l_procid) + mht_exchange.send(p, master_pair_type(target, owning_proc)); + else + mht[target] = owning_proc; + } - for (typename batch_record_map_type::iterator it = batch_map.begin(); - it != batch_map.end(); ++it) { - add_edges(it->second.sources, it->first, it->second.edatas); + // adjust balance according to vertex and edge + proc_balance[owning_proc]++; + proc_balance[owning_proc] += + (degree * float(tot_nverts) / float(tot_nedges)); + + proc_score_incr[owning_proc]++; + proc_score_incr[owning_proc] += + (degree * float(tot_nverts) / float(tot_nedges)); + } + + // periodical synchronize heurisitic + if ((++vcount % interval) == 0) sync_heuristic(); + } + + // last synchronize on mht + mht_exchange.flush(); + master_buffer_type master_buffer; + proc = -1; + while(mht_exchange.recv(proc, master_buffer)) { + foreach(const master_pair_type& pair, master_buffer) + mht[pair.first] = pair.second; } + mht_exchange.clear(); + + +#ifdef TUNING + //logstream(LOG_INFO) << "balance["; + //for (procid_t i = 0; i < nprocs; i++) + // logstream(LOG_INFO) << proc_balance[i] << ","; + //logstream(LOG_INFO) << "] "; + logstream(LOG_INFO) << "nsyncs(" << (vcount / interval) + << ") using " << ti.current_time() << " secs " + << "#mht=" << mht.size() + << std::endl; +#endif } void finalize() { @@ -350,6 +374,9 @@ namespace graphlab { << " #vertices=" << graph.local_graph.num_vertices() << " #edges=" << graph.local_graph.num_edges() << " threshold=" << threshold + << " interval=" << interval + << " gamma=" << gamma + << " alpha=" << alpha << std::endl; } @@ -405,42 +432,13 @@ namespace graphlab { } hybrid_edge_exchange.clear(); } else { - /* send and receive last mht_incr */ - for (typename master_hash_table_type::iterator it = mht_incr.begin(); - it != mht_incr.end(); ++it) { - for (procid_t i = 0; i < nprocs; ++i) { - if (i != l_procid) - mht_exchange.send(i, master_pair_type(it->first, it->second)); - } - mht[it->first] = it->second; - } - mht_incr.clear(); - - mht_exchange.flush(); - master_buffer_type master_buffer; - procid_t proc = -1; - while(mht_exchange.recv(proc, master_buffer)) { - foreach(const master_pair_type& pair, master_buffer) - mht[pair.first] = pair.second; - } - mht_exchange.clear(); - -#ifdef TUNING - if(l_procid == 0) { - logstream(LOG_INFO) << "exchange mapping: " - << ti.current_time() - << " secs" - << std::endl; - } -#endif - high_edge_exchange.flush(); low_edge_exchange.flush(); nedges = low_edge_exchange.size(); hybrid_edges.reserve(nedges + high_edge_exchange.size()); edge_buffer_type edge_buffer; - proc = -1; + procid_t proc = -1; while(low_edge_exchange.recv(proc, edge_buffer)) { foreach(const edge_buffer_record& rec, edge_buffer) { if (mht.find(rec.source) == mht.end()) diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp index 412f37c354..ce948216e0 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp @@ -301,7 +301,7 @@ namespace graphlab { graphlab::timer ti; procid_t l_procid = hybrid_rpc.procid(); size_t high_master = 0, high_mirror = 0, low_master = 0, low_mirror = 0; - + for (size_t lvid = 0; lvid < graph.num_local_vertices(); lvid++) { vertex_record& vrec = graph.lvid2record[lvid]; if (vrec.num_in_edges > threshold) { diff --git a/src/graphlab/graph/ingress/distributed_ingress_base.hpp b/src/graphlab/graph/ingress/distributed_ingress_base.hpp index 9579ee0b5f..9346e919d1 100644 --- a/src/graphlab/graph/ingress/distributed_ingress_base.hpp +++ b/src/graphlab/graph/ingress/distributed_ingress_base.hpp @@ -532,6 +532,12 @@ namespace graphlab { rpc.all_gather(swap_counts); graph.nedges = 0; foreach(size_t count, swap_counts) graph.nedges += count; + if (rpc.procid() == 0) { + size_t max = *std::max_element(swap_counts.begin(), swap_counts.end()); + logstream(LOG_EMPH) << "edges balance: " + << (double) max / ((double) graph.nedges / rpc.numprocs()) + << std::endl; + } // compute vertex count @@ -539,12 +545,25 @@ namespace graphlab { rpc.all_gather(swap_counts); graph.nverts = 0; foreach(size_t count, swap_counts) graph.nverts += count; + if (rpc.procid() == 0) { + size_t max = *std::max_element(swap_counts.begin(), swap_counts.end()); + logstream(LOG_EMPH) << "own vertices balance: " + << (double) max / ((double) graph.nverts / rpc.numprocs()) + << std::endl; + } // compute replicas swap_counts[rpc.procid()] = graph.num_local_vertices(); rpc.all_gather(swap_counts); graph.nreplicas = 0; foreach(size_t count, swap_counts) graph.nreplicas += count; + if (rpc.procid() == 0) { + size_t max = *std::max_element(swap_counts.begin(), swap_counts.end()); + logstream(LOG_EMPH) << "local vertices balance: " + << (double) max / ((double) graph.nreplicas / rpc.numprocs()) + << std::endl; + } + } if (rpc.procid() == 0) { diff --git a/src/graphlab/graph/ingress/ingress_edge_decision.hpp b/src/graphlab/graph/ingress/ingress_edge_decision.hpp index 8419419e9a..d2d0a92840 100644 --- a/src/graphlab/graph/ingress/ingress_edge_decision.hpp +++ b/src/graphlab/graph/ingress/ingress_edge_decision.hpp @@ -40,9 +40,7 @@ namespace graphlab { public: typedef graphlab::vertex_id_type vertex_id_type; typedef distributed_graph graph_type; - typedef fixed_dense_bitset bin_counts_type; - typedef typename boost::unordered_map master_hash_table_type; - + typedef fixed_dense_bitset bin_counts_type; public: /** \brief A decision object for computing the edge assingment. */ @@ -69,46 +67,6 @@ namespace graphlab { return candidates[graph_hash::hash_edge(edge_pair) % (candidates.size())]; }; - /** Assign edges via a heuristic method called ginger */ - procid_t edge_to_proc_ginger (const std::vector& sources, - const vertex_id_type target, - master_hash_table_type& mht, - master_hash_table_type& mht_incr, - std::vector& proc_balance, - double alpha, - double gamma) { - size_t numprocs = proc_balance.size(); - - // Compute the score of each proc. - procid_t best_proc = -1; - double maxscore = 0.0; - std::vector proc_score(numprocs); - std::vector proc_degrees(numprocs); - - for (size_t i = 0; i < sources.size(); ++i) { - if (mht.find(sources[i]) != mht.end()) - proc_degrees[mht[sources[i]]]++; - else if (mht_incr.find(sources[i]) != mht_incr.end()) - proc_degrees[mht_incr[sources[i]]]++; - } - - for (size_t i = 0; i < numprocs; ++i) { - proc_score[i] = proc_degrees[i] - - alpha * gamma * pow(proc_balance[i], (gamma - 1)); - } - - // TODO: just use std::max - maxscore = *std::max_element(proc_score.begin(), proc_score.end()); - for (size_t i = 0; i < numprocs; ++i) { - if (proc_score[i] == maxscore) { - best_proc = i; - break; - } - } - - proc_balance[best_proc]++; - return best_proc; - }; /** Greedy assign (source, target) to a machine using: * bitset src_degree : the degree presence of source over machines From 5f7aa8f37975dafd74e4991204d133f5ecda61b6 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Sun, 29 Dec 2013 18:06:48 +0800 Subject: [PATCH 29/50] sweep plsync code --- src/graphlab/engine/powerlyra_sync_engine.hpp | 52 ++++++++----------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/src/graphlab/engine/powerlyra_sync_engine.hpp b/src/graphlab/engine/powerlyra_sync_engine.hpp index 19d446c139..60eed5191e 100644 --- a/src/graphlab/engine/powerlyra_sync_engine.hpp +++ b/src/graphlab/engine/powerlyra_sync_engine.hpp @@ -48,7 +48,6 @@ #include #include -#include #include @@ -58,8 +57,6 @@ #include -#define ALIGN_DOWN(_n, _w) ((_n) & (~((_w)-1))) - #define TUNING namespace graphlab { @@ -207,8 +204,6 @@ namespace graphlab { * \see graphlab::omni_engine * \see graphlab::async_consistent_engine * \see graphlab::semi_synchronous_engine - * \see graphlab::power_sync_engine - * \see graphlab::lyra_sync_engine * \see graphlab::powerlyra_sync_engine */ template @@ -724,10 +719,6 @@ namespace graphlab { // documentation inherited from iengine float elapsed_seconds() const; - // documentation inherited from iengine - double execution_time() const; - - /** * \brief Get the current iteration number since start was last * invoked. @@ -788,7 +779,7 @@ namespace graphlab { * * This function is called by the \ref graphlab::context. * - * @param [in] lvid the local vertex id of the vertex to signal + * @param [in] vertex the vertex to signal * @param [in] message the message to send to that vertex. */ void internal_signal(const vertex_type& vertex, @@ -1345,10 +1336,6 @@ namespace graphlab { float powerlyra_sync_engine:: elapsed_seconds() const { return timer::approx_time_seconds() - start_time; } - template - double powerlyra_sync_engine:: - execution_time() const { return exec_time; } - template int powerlyra_sync_engine:: iteration() const { return iteration_counter; } @@ -1424,9 +1411,8 @@ namespace graphlab { // Exchange Messages -------------------------------------------------- - // Powergraph: send messages from replicas to master - // - set messages and has_message - // Lyra: none + // High: send messages from mirrors to master + // Low: none (if only IN_EDGES) // // if (rmi.procid() == 0) std::cout << "Exchange messages..." << std::endl; bk_ti.start(); @@ -1442,9 +1428,9 @@ namespace graphlab { // 2. call init and gather_edges // 3. set active_superstep, active_minorstep and edge_dirs // 4. clear has_message - // Powergraph: send vprog and edge_dirs from master to replicas - // - set vprog, edge_dirs and set active_minorstep - // Lyra: none + // + // High: send vprog and edge_dirs from master to mirrors + // Low: none (if only IN_EDGES) // // if (rmi.procid() == 0) std::cout << "Receive messages..." << std::endl; bk_ti.start(); @@ -1475,13 +1461,14 @@ namespace graphlab { break; } + // Execute gather operations------------------------------------------- // 1. call pre_local_gather, gather and post_local_gather // 2. (master) set gather_accum and has_gather_accum // 3. clear active_minorstep - // Powergraph: send gather_accum from replicas to master - // - set gather_accum and has_gather_accum - // Lyra: none + // + // High: send gather_accum from mirrors to master + // Low: none (if only IN_EDGES) // // if (rmi.procid() == 0) std::cout << "Gathering..." << std::endl; bk_ti.start(); @@ -1503,7 +1490,6 @@ namespace graphlab { // 1. call apply and scatter_edges // 2. set edge_dirs and active_minorstep // 3. send vdata, vprog and edge_dirs from master to replicas - // - set vdata, vprog, edge_dirs and active_minorstep // // if (rmi.procid() == 0) std::cout << "Applying..." << std::endl; bk_ti.start(); @@ -1637,10 +1623,10 @@ namespace graphlab { void powerlyra_sync_engine:: exchange_messages(const size_t thread_id) { context_type context(*this, graph); + fixed_dense_bitset<8 * sizeof(size_t)> local_bitset; // a word-size = 64 bit const size_t TRY_RECV_MOD = 100; size_t vcount = 0; - fixed_dense_bitset<8 * sizeof(size_t)> local_bitset; // a word-size = 64 bit - + while (1) { // increment by a word at a time lvid_type lvid_block_start = @@ -1724,14 +1710,14 @@ namespace graphlab { send_activs(lvid, thread_id); } } + if(++vcount % TRY_RECV_MOD == 0) recv_activs(); } - if(++vcount % TRY_RECV_MOD == 0) recv_activs(); } num_active_vertices += nactive_inc; activ_exchange.partial_flush(); thread_barrier.wait(); // Flush the buffer and finish receiving any remaining activations. - if(thread_id == 0) activ_exchange.flush(); // call full_barrier + if(thread_id == 0) activ_exchange.flush(); thread_barrier.wait(); recv_activs(); } // end of receive_messages @@ -1784,6 +1770,7 @@ namespace graphlab { if (gather_dir == IN_EDGES || gather_dir == ALL_EDGES) { foreach(local_edge_type local_edge, local_vertex.in_edges()) { edge_type edge(local_edge); + // elocks[local_edge.id()].lock(); if(accum_is_set) { // \todo hint likely accum += vprog.gather(context, vertex, edge); } else { @@ -1791,12 +1778,14 @@ namespace graphlab { accum_is_set = true; } ++edges_touched; + // elocks[local_edge.id()].unlock(); } } // end of if in_edges/all_edges // Loop over out edges if(gather_dir == OUT_EDGES || gather_dir == ALL_EDGES) { foreach(local_edge_type local_edge, local_vertex.out_edges()) { edge_type edge(local_edge); + // elocks[local_edge.id()].lock(); if(accum_is_set) { // \todo hint likely accum += vprog.gather(context, vertex, edge); } else { @@ -1804,6 +1793,7 @@ namespace graphlab { accum_is_set = true; } ++edges_touched; + // elocks[local_edge.id()].unlock(); } } // end of if out_edges/all_edges INCREMENT_EVENT(EVENT_GATHERS, edges_touched); @@ -1834,7 +1824,7 @@ namespace graphlab { per_thread_compute_time[thread_id] += ti.current_time(); accum_exchange.partial_flush(); thread_barrier.wait(); - if(thread_id == 0) accum_exchange.flush(); // full_barrier + if(thread_id == 0) accum_exchange.flush(); thread_barrier.wait(); recv_accums(); } // end of execute_gathers @@ -1939,7 +1929,9 @@ namespace graphlab { if(scatter_dir == IN_EDGES || scatter_dir == ALL_EDGES) { foreach(local_edge_type local_edge, local_vertex.in_edges()) { edge_type edge(local_edge); + // elocks[local_edge.id()].lock(); vprog.scatter(context, vertex, edge); + // elocks[local_edge.id()].unlock(); ++edges_touched; } } // end of if in_edges/all_edges @@ -1947,7 +1939,9 @@ namespace graphlab { if(scatter_dir == OUT_EDGES || scatter_dir == ALL_EDGES) { foreach(local_edge_type local_edge, local_vertex.out_edges()) { edge_type edge(local_edge); + // elocks[local_edge.id()].lock(); vprog.scatter(context, vertex, edge); + // elocks[local_edge.id()].unlock(); ++edges_touched; } } // end of if out_edges/all_edges From 778d2b9b85af7fc1ac57489cef7c9aa489489236 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Sun, 29 Dec 2013 22:17:29 +0800 Subject: [PATCH 30/50] optimize pl engine for no dynamic computation --- src/graphlab/engine/powerlyra_sync_engine.hpp | 213 +++++++++++++----- 1 file changed, 158 insertions(+), 55 deletions(-) diff --git a/src/graphlab/engine/powerlyra_sync_engine.hpp b/src/graphlab/engine/powerlyra_sync_engine.hpp index 60eed5191e..0ea3a9fe0a 100644 --- a/src/graphlab/engine/powerlyra_sync_engine.hpp +++ b/src/graphlab/engine/powerlyra_sync_engine.hpp @@ -44,7 +44,6 @@ #include #include #include -#include #include #include @@ -57,7 +56,7 @@ #include -#define TUNING +#undef TUNING namespace graphlab { @@ -456,10 +455,13 @@ namespace graphlab { std::vector vlocks; /** - * \brief The egde dirs associated with each vertex on this - * machine. + * \brief The elocks protect individual edges during gather and + * scatter. Technically there is a potential race since gather + * and scatter can modify edge values and can overlap. The edge + * lock ensures that only one gather or scatter occurs on an edge + * at a time. */ - std::vector edge_dirs; + std::vector elocks; /** * \brief The vertex programs associated with each vertex on this @@ -585,28 +587,52 @@ namespace graphlab { activ_exchange_type activ_exchange; /** - * \brief The tetrad type used to update vertex data and activate mirrors. + * \brief The triple type used to update vertex data and activate neighbors. */ - typedef tetrad vid_vdata_edir_vprog_tetrad_type; + typedef triple + vid_vdata_vprog_triple_type; /** - * \brief The type of the express used to update mirrors + * \brief The type of the exchange used to update mirrors */ - typedef fiber_buffered_exchange - update_exchange_type; + typedef fiber_buffered_exchange + update_activ_exchange_type; /** - * \brief The type of buffer used by the express to update mirrors + * \brief The type of buffer used by the exchange to update mirrors */ - typedef typename update_exchange_type::buffer_type update_buffer_type; + typedef typename update_activ_exchange_type::buffer_type + update_activ_buffer_type; + + /** + * \brief The distributed express used to update mirrors + * vertex programs. + */ + update_activ_exchange_type update_activ_exchange; + + + /** + * \brief The triple type used to only update vertex data. + */ + typedef std::pair vid_vdata_pair_type; + + /** + * \brief The type of the express used to update mirrors + */ + typedef fiber_buffered_exchange update_exchange_type; + /** + * \brief The type of buffer used by the exchange to update mirrors + */ + typedef typename update_exchange_type::buffer_type update_buffer_type; + /** * \brief The distributed express used to update mirrors * vertex programs. */ update_exchange_type update_exchange; + /** * \brief The pair type used to synchronize the results of the gather phase */ @@ -960,6 +986,24 @@ namespace graphlab { */ void recv_activs(); + /** + * \brief Send the update messages (vertex data, program) + * for the local vertex id to all of its mirrors. + * + * @param [in] lvid the vertex to sync. It must be the master of that vertex. + */ + void send_updates_activs(lvid_type lvid, size_t thread_id); + + /** + * \brief do update and activation to local mirros. + * + * This function returns when there is nothing left in the + * buffered exchange and should be called after the buffered + * exchange has been flushed + */ + void recv_updates_activs(); + + /** * \brief Send the update messages (vertex data, program and edge set) * for the local vertex id to all of its mirrors. @@ -971,8 +1015,9 @@ namespace graphlab { /** * \brief do update to local mirros. * - * This function is a callback of express, and will be invoked when receives - * update message. + * This function returns when there is nothing left in the + * buffered exchange and should be called after the buffered + * exchange has been flushed */ void recv_updates(); @@ -1000,8 +1045,7 @@ namespace graphlab { * * @param [in] lvid the vertex to send */ - void send_message(lvid_type lvid, const message_type& message, - const size_t thread_id); + void send_message(lvid_type lvid, const size_t thread_id); /** * \brief Receive the scatter messages from the buffered exchange. @@ -1070,6 +1114,7 @@ namespace graphlab { max_iterations(-1), snapshot_interval(-1), iteration_counter(0), print_interval(5), timeout(0), sched_allv(false), activ_exchange(dc), + update_activ_exchange(dc), update_exchange(dc), accum_exchange(dc), message_exchange(dc), @@ -1164,7 +1209,6 @@ namespace graphlab { // Allocate vertex locks and vertex programs vlocks.resize(l_nverts); vertex_programs.resize(l_nverts); - edge_dirs.resize(l_nverts); // Allocate messages and message bitset messages.resize(l_nverts, message_type()); @@ -1364,9 +1408,11 @@ namespace graphlab { // Reset event log counters? // Start the timer start_time = timer::approx_time_seconds(); +#ifdef TUNING exec_time = exch_time = recv_time = gather_time = apply_time = scatter_time = 0.0; graphlab::timer ti, bk_ti; +#endif iteration_counter = 0; force_abort = false; execution_status::status_enum termination_reason = execution_status::UNSET; @@ -1385,7 +1431,9 @@ namespace graphlab { } // Program Main loop ==================================================== +#ifdef TUNING ti.start(); +#endif while(iteration_counter < max_iterations && !force_abort ) { // Check first to see if we are out of time @@ -1415,9 +1463,13 @@ namespace graphlab { // Low: none (if only IN_EDGES) // // if (rmi.procid() == 0) std::cout << "Exchange messages..." << std::endl; +#ifdef TUNING bk_ti.start(); +#endif run_synchronous( &powerlyra_sync_engine::exchange_messages ); +#ifdef TUNING exch_time += bk_ti.current_time(); +#endif /** * Post conditions: * 1) master (high and low) vertices have messages @@ -1433,11 +1485,15 @@ namespace graphlab { // Low: none (if only IN_EDGES) // // if (rmi.procid() == 0) std::cout << "Receive messages..." << std::endl; +#ifdef TUNING bk_ti.start(); +#endif run_synchronous( &powerlyra_sync_engine::receive_messages ); if (sched_allv) active_minorstep.fill(); has_message.clear(); +#ifdef TUNING recv_time += bk_ti.current_time(); +#endif /** * Post conditions: * 1) there are no messages remaining @@ -1471,13 +1527,17 @@ namespace graphlab { // Low: none (if only IN_EDGES) // // if (rmi.procid() == 0) std::cout << "Gathering..." << std::endl; +#ifdef TUNING bk_ti.start(); +#endif run_synchronous( &powerlyra_sync_engine::execute_gathers ); // Clear the minor step bit since only super-step vertices // (only master vertices are required to participate in the // apply step) active_minorstep.clear(); +#ifdef TUNING gather_time += bk_ti.current_time(); +#endif /** * Post conditions: * 1) gather_accum for all master vertices contains the @@ -1492,9 +1552,13 @@ namespace graphlab { // 3. send vdata, vprog and edge_dirs from master to replicas // // if (rmi.procid() == 0) std::cout << "Applying..." << std::endl; +#ifdef TUNING bk_ti.start(); +#endif run_synchronous( &powerlyra_sync_engine::execute_applys ); +#ifdef TUNING apply_time += bk_ti.current_time(); +#endif /** * Post conditions: * 1) any changes to the vertex data have been synchronized @@ -1511,15 +1575,19 @@ namespace graphlab { // 1. call scatter (signal: set messages and has_message) // // if (rmi.procid() == 0) std::cout << "Scattering..." << std::endl; +#ifdef TUNING bk_ti.start(); +#endif run_synchronous( &powerlyra_sync_engine::execute_scatters ); +#ifdef TUNING scatter_time += bk_ti.current_time(); +#endif /** * Post conditions: * 1) NONE */ if(rmi.procid() == 0 && print_this_round) - logstream(LOG_DEBUG) << "\t Running Aggregators" << std::endl; + logstream(LOG_EMPH) << "\t Running Aggregators" << std::endl; // probe the aggregator aggregator.tick_synchronous(); @@ -1529,7 +1597,9 @@ namespace graphlab { graph.save_binary(snapshot_path); } } +#ifdef TUNING exec_time = ti.current_time(); +#endif if (rmi.procid() == 0) { logstream(LOG_EMPH) << iteration_counter @@ -1570,6 +1640,7 @@ namespace graphlab { for (size_t i = 0;i < all_compute_time_vec.size(); ++i) { logstream(LOG_INFO) << all_compute_time_vec[i] << " "; } + logstream(LOG_INFO) << std::endl; #ifdef TUNING logstream(LOG_INFO) << "Total Calls(G|A|S): " << completed_gathers.value << "|" @@ -1643,9 +1714,8 @@ namespace graphlab { if (lvid >= graph.num_local_vertices()) break; // [TARGET]: High/Low-degree Mirrors - // only if scatter via in-edges will set has_message of low_mirror if(!graph.l_is_master(lvid)) { - send_message(lvid, messages[lvid], thread_id); + send_message(lvid, thread_id); has_message.clear_bit(lvid); // clear the message to save memory messages[lvid] = message_type(); @@ -1655,6 +1725,7 @@ namespace graphlab { } } // end of loop over vertices to send messages message_exchange.partial_flush(); + // Finish sending and receiving all messages thread_barrier.wait(); if(thread_id == 0) message_exchange.flush(); thread_barrier.wait(); @@ -1686,7 +1757,6 @@ namespace graphlab { lvid_type lvid = lvid_block_start + lvid_block_offset; if (lvid >= graph.num_local_vertices()) break; - // [TARGET]: High/Low-degree Masters ASSERT_TRUE(graph.l_is_master(lvid)); // The vertex becomes active for this superstep active_superstep.set_bit(lvid); @@ -1699,14 +1769,14 @@ namespace graphlab { if (sched_allv) continue; // Determine if the gather should be run const vertex_program_type& const_vprog = vertex_programs[lvid]; - edge_dirs[lvid] = const_vprog.gather_edges(context, vertex); - if(edge_dirs[lvid] != graphlab::NO_EDGES) { + edge_dir_type gather_dir = const_vprog.gather_edges(context, vertex); + if(gather_dir != graphlab::NO_EDGES) { active_minorstep.set_bit(lvid); // send Gx1 msgs if (high_master_lvid(lvid) || (low_master_lvid(lvid) // only if gather via out-edge - && ((edge_dirs[lvid] == graphlab::OUT_EDGES) - || (edge_dirs[lvid] == graphlab::ALL_EDGES)))) { + && ((gather_dir == graphlab::OUT_EDGES) + || (gather_dir == graphlab::ALL_EDGES)))) { send_activs(lvid, thread_id); } } @@ -1715,6 +1785,8 @@ namespace graphlab { } num_active_vertices += nactive_inc; activ_exchange.partial_flush(); + // Flush the buffer and finish receiving any remaining vertex + // programs. thread_barrier.wait(); // Flush the buffer and finish receiving any remaining activations. if(thread_id == 0) activ_exchange.flush(); @@ -1777,8 +1849,8 @@ namespace graphlab { accum = vprog.gather(context, vertex, edge); accum_is_set = true; } - ++edges_touched; // elocks[local_edge.id()].unlock(); + ++edges_touched; } } // end of if in_edges/all_edges // Loop over out edges @@ -1792,8 +1864,8 @@ namespace graphlab { accum = vprog.gather(context, vertex, edge); accum_is_set = true; } - ++edges_touched; // elocks[local_edge.id()].unlock(); + ++edges_touched; } } // end of if out_edges/all_edges INCREMENT_EVENT(EVENT_GATHERS, edges_touched); @@ -1823,6 +1895,7 @@ namespace graphlab { completed_gathers += ngather_inc; per_thread_compute_time[thread_id] += ti.current_time(); accum_exchange.partial_flush(); + // Finish sending and receiving all gather operations thread_barrier.wait(); if(thread_id == 0) accum_exchange.flush(); thread_barrier.wait(); @@ -1871,27 +1944,34 @@ namespace graphlab { // determine if a scatter operation is needed const vertex_program_type& const_vprog = vertex_programs[lvid]; const vertex_type const_vertex = vertex; - edge_dirs[lvid] = const_vprog.scatter_edges(context, const_vertex); - - if (edge_dirs[lvid] != graphlab::NO_EDGES) + + if (const_vprog.scatter_edges(context, const_vertex) + != graphlab::NO_EDGES) { + // send Ax1 and Sx1 + send_updates_activs(lvid, thread_id); active_minorstep.set_bit(lvid); - else + } else { + // send Ax1 + send_updates(lvid, thread_id); vertex_programs[lvid] = vertex_program_type(); + } - // send Ax1 and Sx1 - send_updates(lvid, thread_id); - - if(++vcount % TRY_RECV_MOD == 0) recv_updates(); + if(++vcount % TRY_RECV_MOD == 0) { + recv_updates_activs(); recv_updates(); + } } } // end of loop over vertices to run apply completed_applys += napply_inc; per_thread_compute_time[thread_id] += ti.current_time(); - update_exchange.partial_flush(); + update_activ_exchange.partial_flush(); update_exchange.partial_flush(); thread_barrier.wait(); // Flush the buffer and finish receiving any remaining updates. - if(thread_id == 0) update_exchange.flush(); // full_barrier + if(thread_id == 0) { + update_activ_exchange.flush(); update_exchange.flush(); + } thread_barrier.wait(); - recv_updates(); + recv_updates_activs(); recv_updates(); + } // end of execute_applys @@ -1922,7 +2002,7 @@ namespace graphlab { const vertex_program_type& vprog = vertex_programs[lvid]; local_vertex_type local_vertex = graph.l_vertex(lvid); const vertex_type vertex(local_vertex); - const edge_dir_type scatter_dir = edge_dirs[lvid]; + const edge_dir_type scatter_dir = vprog.scatter_edges(context, vertex); size_t edges_touched = 0; // Loop over in edges @@ -1989,35 +2069,58 @@ namespace graphlab { template inline void powerlyra_sync_engine:: - send_updates(lvid_type lvid, const size_t thread_id) { + send_updates_activs(lvid_type lvid, const size_t thread_id) { ASSERT_TRUE(graph.l_is_master(lvid)); const vertex_id_type vid = graph.global_vid(lvid); local_vertex_type vertex = graph.l_vertex(lvid); foreach(const procid_t& mirror, vertex.mirrors()) { - update_exchange.send(mirror, - make_tetrad(vid, + update_activ_exchange.send(mirror, + make_triple(vid, vertex.data(), - edge_dirs[lvid], vertex_programs[lvid])); } } // end of send_update + template + inline void powerlyra_sync_engine:: + recv_updates_activs() { + typename update_activ_exchange_type::recv_buffer_type recv_buffer; + while(update_activ_exchange.recv(recv_buffer)) { + for (size_t i = 0;i < recv_buffer.size(); ++i) { + update_activ_buffer_type& buffer = recv_buffer[i].buffer; + foreach(const vid_vdata_vprog_triple_type& t, buffer) { + const lvid_type lvid = graph.local_vid(t.first); + ASSERT_FALSE(graph.l_is_master(lvid)); + graph.l_vertex(lvid).data() = t.second; + vertex_programs[lvid] = t.third; + active_minorstep.set_bit(lvid); + } + } + } + } // end of recv_updates + + template + inline void powerlyra_sync_engine:: + send_updates(lvid_type lvid, const size_t thread_id) { + ASSERT_TRUE(graph.l_is_master(lvid)); + const vertex_id_type vid = graph.global_vid(lvid); + local_vertex_type vertex = graph.l_vertex(lvid); + foreach(const procid_t& mirror, vertex.mirrors()) { + update_exchange.send(mirror, std::make_pair(vid, vertex.data())); + } + } // end of send_update + template inline void powerlyra_sync_engine:: recv_updates() { typename update_exchange_type::recv_buffer_type recv_buffer; while(update_exchange.recv(recv_buffer)) { for (size_t i = 0;i < recv_buffer.size(); ++i) { - typename update_exchange_type::buffer_type& buffer = recv_buffer[i].buffer; - foreach(const vid_vdata_edir_vprog_tetrad_type& t, buffer) { - const lvid_type lvid = graph.local_vid(t.first); + update_buffer_type& buffer = recv_buffer[i].buffer; + foreach(const vid_vdata_pair_type& pair, buffer) { + const lvid_type lvid = graph.local_vid(pair.first); ASSERT_FALSE(graph.l_is_master(lvid)); - graph.l_vertex(lvid).data() = t.second; - if (t.third != graphlab::NO_EDGES) { - edge_dirs[lvid] = t.third; - vertex_programs[lvid] = t.fourth; - active_minorstep.set_bit(lvid); - } + graph.l_vertex(lvid).data() = pair.second; } } } @@ -2069,11 +2172,11 @@ namespace graphlab { template inline void powerlyra_sync_engine:: - send_message(lvid_type lvid, const message_type& message, const size_t thread_id) { + send_message(lvid_type lvid, const size_t thread_id) { ASSERT_FALSE(graph.l_is_master(lvid)); const procid_t master = graph.l_master(lvid); const vertex_id_type vid = graph.global_vid(lvid); - message_exchange.send(master, std::make_pair(vid, message)); + message_exchange.send(master, std::make_pair(vid, messages[lvid])); } // end of send_message template From 21e3dc4924557555c104033fec0c3348dd7274c8 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Tue, 31 Dec 2013 14:37:14 +0800 Subject: [PATCH 31/50] performance bug, meaningless call recv_message --- src/graphlab/engine/powerlyra_sync_engine.hpp | 2 +- .../graph/ingress/distributed_hybrid_ginger_ingress.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/graphlab/engine/powerlyra_sync_engine.hpp b/src/graphlab/engine/powerlyra_sync_engine.hpp index 0ea3a9fe0a..a2208d731f 100644 --- a/src/graphlab/engine/powerlyra_sync_engine.hpp +++ b/src/graphlab/engine/powerlyra_sync_engine.hpp @@ -1696,7 +1696,7 @@ namespace graphlab { context_type context(*this, graph); fixed_dense_bitset<8 * sizeof(size_t)> local_bitset; // a word-size = 64 bit const size_t TRY_RECV_MOD = 100; - size_t vcount = 0; + size_t vcount = 1; // avoid unnecessarily call recv_messages() while (1) { // increment by a word at a time diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp index 7e0f29be1f..161fbcb948 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp @@ -164,7 +164,7 @@ namespace graphlab { ASSERT_GT(tot_nedges, 0); ASSERT_GT(tot_nverts, 0); gamma = 1.5; - alpha = sqrt(dc.numprocs()) * tot_nedges / pow(tot_nverts, gamma); + alpha = sqrt(dc.numprocs()) * double(tot_nedges) / pow(tot_nverts, gamma); /* fast pass for standalone case. */ standalone = hybrid_rpc.numprocs() == 1; From b44cc379483f6b78c9f9400f761606949cba4cce Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Thu, 20 Mar 2014 00:03:52 +0800 Subject: [PATCH 32/50] refine dtype, improve compatible, now plsync can work with all ingress algorithms --- src/graphlab/engine/powerlyra_sync_engine.hpp | 43 +++++-------------- src/graphlab/graph/distributed_graph.hpp | 8 ++-- .../distributed_hybrid_ginger_ingress.hpp | 20 +++------ .../ingress/distributed_hybrid_ingress.hpp | 20 +++------ 4 files changed, 27 insertions(+), 64 deletions(-) diff --git a/src/graphlab/engine/powerlyra_sync_engine.hpp b/src/graphlab/engine/powerlyra_sync_engine.hpp index a2208d731f..44fb43fa10 100644 --- a/src/graphlab/engine/powerlyra_sync_engine.hpp +++ b/src/graphlab/engine/powerlyra_sync_engine.hpp @@ -904,10 +904,8 @@ namespace graphlab { } } // end of run_synchronous - inline bool high_master_lvid(const lvid_type lvid); - inline bool low_master_lvid(const lvid_type lvid); - inline bool high_mirror_lvid(const lvid_type lvid); - inline bool low_mirror_lvid(const lvid_type lvid); + inline bool high_lvid(const lvid_type lvid); + inline bool low_lvid(const lvid_type lvid); // /** // * \brief Initialize all vertex programs by invoking @@ -1168,14 +1166,7 @@ namespace graphlab { ADD_CUMULATIVE_EVENT(EVENT_GATHERS , "Gathers", "Calls"); ADD_CUMULATIVE_EVENT(EVENT_SCATTERS , "Scatters", "Calls"); ADD_INSTANTANEOUS_EVENT(EVENT_ACTIVE_CPUS, "Active Threads", "Threads"); - - // Graph should has been finalized - ASSERT_TRUE(graph.is_finalized()); - // Only support zone cuts - ASSERT_TRUE(graph.get_cuts_type() == graph_type::HYBRID_CUTS - || graph.get_cuts_type() == graph_type::HYBRID_GINGER_CUTS); - // if (rmi.procid() == 0) graph.dump_graph_info(); - + graph.finalize(); init(); } // end of powerlyra_sync_engine @@ -1668,26 +1659,14 @@ namespace graphlab { template inline bool powerlyra_sync_engine:: - high_master_lvid(const lvid_type lvid) { - return graph.l_degree_type(lvid) == graph_type::HIGH_MASTER; - } - - template - inline bool powerlyra_sync_engine:: - low_master_lvid(const lvid_type lvid) { - return graph.l_degree_type(lvid) == graph_type::LOW_MASTER; - } - - template - inline bool powerlyra_sync_engine:: - high_mirror_lvid(const lvid_type lvid) { - return graph.l_degree_type(lvid) == graph_type::HIGH_MIRROR; + high_lvid(const lvid_type lvid) { + return graph.l_degree_type(lvid) == graph_type::HIGH; } template inline bool powerlyra_sync_engine:: - low_mirror_lvid(const lvid_type lvid) { - return graph.l_degree_type(lvid) == graph_type::LOW_MIRROR; + low_lvid(const lvid_type lvid) { + return graph.l_degree_type(lvid) == graph_type::LOW; } template @@ -1773,10 +1752,10 @@ namespace graphlab { if(gather_dir != graphlab::NO_EDGES) { active_minorstep.set_bit(lvid); // send Gx1 msgs - if (high_master_lvid(lvid) - || (low_master_lvid(lvid) // only if gather via out-edge - && ((gather_dir == graphlab::OUT_EDGES) - || (gather_dir == graphlab::ALL_EDGES)))) { + if (high_lvid(lvid) + || (low_lvid(lvid) // only if gather via out-edge + && ((gather_dir == graphlab::ALL_EDGES) + || (gather_dir == graphlab::OUT_EDGES)))) { send_activs(lvid, thread_id); } } diff --git a/src/graphlab/graph/distributed_graph.hpp b/src/graphlab/graph/distributed_graph.hpp index 6c83b39770..0e17c08d24 100644 --- a/src/graphlab/graph/distributed_graph.hpp +++ b/src/graphlab/graph/distributed_graph.hpp @@ -420,8 +420,7 @@ namespace graphlab { typedef graphlab::lvid_type lvid_type; typedef graphlab::edge_id_type edge_id_type; - enum degree_type {HIGH_MASTER = 0, LOW_MASTER, - HIGH_MIRROR, LOW_MIRROR, NUM_ZONE_TYPES}; + enum degree_type {HIGH = 0, LOW, NUM_ZONE_TYPES}; enum cuts_type {VERTEX_CUTS = 0, EDGE_CUTS, HYBRID_CUTS, HYBRID_GINGER_CUTS, NUM_CUTS_TYPES}; @@ -2676,9 +2675,9 @@ namespace graphlab { NOT be in this set.*/ mirror_type _mirrors; vertex_record() : - owner(-1), gvid(-1), num_in_edges(0), num_out_edges(0) { } + owner(-1), dtype(HIGH), gvid(-1), num_in_edges(0), num_out_edges(0) { } vertex_record(const vertex_id_type& vid) : - owner(-1), gvid(vid), num_in_edges(0), num_out_edges(0) { } + owner(-1), dtype(HIGH), gvid(vid), num_in_edges(0), num_out_edges(0) { } procid_t get_owner () const { return owner; } const mirror_type& mirrors() const { return _mirrors; } size_t num_mirrors() const { return _mirrors.popcount(); } @@ -2709,6 +2708,7 @@ namespace graphlab { bool operator==(const vertex_record& other) const { return ( (owner == other.owner) && + (dtype == other.dtype) && (gvid == other.gvid) && (num_in_edges == other.num_in_edges) && (num_out_edges == other.num_out_edges) && diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp index 161fbcb948..eef3e53504 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp @@ -524,21 +524,13 @@ namespace graphlab { for (size_t lvid = 0; lvid < graph.num_local_vertices(); lvid++) { vertex_record& vrec = graph.lvid2record[lvid]; if (vrec.num_in_edges > threshold) { - if (vrec.owner == l_procid) { - vrec.dtype = graph_type::HIGH_MASTER; - high_master ++; - } else { - vrec.dtype = graph_type::HIGH_MIRROR; - high_mirror ++; - } + vrec.dtype = graph_type::HIGH; + if (vrec.owner == l_procid) high_master ++; + else high_mirror ++; } else { - if (vrec.owner == l_procid) { - vrec.dtype = graph_type::LOW_MASTER; - low_master ++; - } else { - vrec.dtype = graph_type::LOW_MIRROR; - low_mirror ++; - } + vrec.dtype = graph_type::LOW; + if (vrec.owner == l_procid) low_master ++; + else low_mirror ++; } } diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp index ce948216e0..b3b693a2f4 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp @@ -305,21 +305,13 @@ namespace graphlab { for (size_t lvid = 0; lvid < graph.num_local_vertices(); lvid++) { vertex_record& vrec = graph.lvid2record[lvid]; if (vrec.num_in_edges > threshold) { - if (vrec.owner == l_procid) { - vrec.dtype = graph_type::HIGH_MASTER; - high_master ++; - } else { - vrec.dtype = graph_type::HIGH_MIRROR; - high_mirror ++; - } + vrec.dtype = graph_type::HIGH; + if (vrec.owner == l_procid) high_master ++; + else high_mirror ++; } else { - if (vrec.owner == l_procid) { - vrec.dtype = graph_type::LOW_MASTER; - low_master ++; - } else { - vrec.dtype = graph_type::LOW_MIRROR; - low_mirror ++; - } + vrec.dtype = graph_type::LOW; + if (vrec.owner == l_procid) low_master ++; + else low_mirror ++; } } From cee6cba8853ad0a419ac4ab9e918a6353e855fb8 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Thu, 20 Mar 2014 00:04:28 +0800 Subject: [PATCH 33/50] powerlyra's async engine --- .../engine/async_consistent_engine.hpp | 5 +- src/graphlab/engine/omni_engine.hpp | 11 +- .../engine/powerlyra_async_engine.hpp | 1320 +++++++++++++++++ src/graphlab/rpc/fiber_async_consensus.hpp | 2 +- 4 files changed, 1335 insertions(+), 3 deletions(-) create mode 100755 src/graphlab/engine/powerlyra_async_engine.hpp diff --git a/src/graphlab/engine/async_consistent_engine.hpp b/src/graphlab/engine/async_consistent_engine.hpp index 9794098978..1b533154e8 100644 --- a/src/graphlab/engine/async_consistent_engine.hpp +++ b/src/graphlab/engine/async_consistent_engine.hpp @@ -831,7 +831,10 @@ namespace graphlab { logstream(LOG_DEBUG) << rmi.procid() << "-" << threadid << ": " << "\tTermination Double Checked" << std::endl; - if (!endgame_mode) logstream(LOG_EMPH) << "Endgame mode\n"; + if (!endgame_mode) + logstream(LOG_EMPH) << rmi.procid() << " Endgame mode " + << (timer::approx_time_seconds() - engine_start_time) + << std::endl; endgame_mode = true; // put everyone in endgame for (procid_t i = 0;i < rmi.dc().numprocs(); ++i) { diff --git a/src/graphlab/engine/omni_engine.hpp b/src/graphlab/engine/omni_engine.hpp index 71ddc8cf96..a7ee946c01 100644 --- a/src/graphlab/engine/omni_engine.hpp +++ b/src/graphlab/engine/omni_engine.hpp @@ -32,6 +32,7 @@ #include #include #include +#include namespace graphlab { @@ -142,6 +143,11 @@ namespace graphlab { */ typedef powerlyra_sync_engine powerlyra_sync_engine_type; + /** + * \brief the type of asynchronous engine + */ + typedef powerlyra_async_engine powerlyra_async_engine_type; + private: @@ -197,11 +203,14 @@ namespace graphlab { logstream(LOG_INFO) << "Using the Synchronous engine." << std::endl; engine_ptr = new synchronous_engine_type(dc, graph, new_options); } else if(engine_type == "async" || engine_type == "asynchronous") { - logstream(LOG_INFO) << "Using the Synchronous engine." << std::endl; + logstream(LOG_INFO) << "Using the Asynchronous engine." << std::endl; engine_ptr = new async_consistent_engine_type(dc, graph, new_options); } else if(engine_type == "plsync" || engine_type == "powerlyra_synchronous") { logstream(LOG_INFO) << "Using the PowerLyra Synchronous engine." << std::endl; engine_ptr = new powerlyra_sync_engine_type(dc, graph, new_options); + } else if(engine_type == "plasync" || engine_type == "powerlyra_asynchronous") { + logstream(LOG_INFO) << "Using the PowerLyra Asynchronous engine." << std::endl; + engine_ptr = new powerlyra_async_engine_type(dc, graph, new_options); } else { logstream(LOG_FATAL) << "Invalid engine type: " << engine_type << std::endl; } diff --git a/src/graphlab/engine/powerlyra_async_engine.hpp b/src/graphlab/engine/powerlyra_async_engine.hpp new file mode 100755 index 0000000000..abafc9995f --- /dev/null +++ b/src/graphlab/engine/powerlyra_async_engine.hpp @@ -0,0 +1,1320 @@ +/** + * Copyright (c) 2009 Carnegie Mellon University. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * For more about this software visit: + * + * http://www.graphlab.ml.cmu.edu + * + */ + + + + +#ifndef GRAPHLAB_POWERLYRA_ASYNC_ENGINE_HPP +#define GRAPHLAB_POWERLYRA_ASYNC_ENGINE_HPP + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +namespace graphlab { + + + /** + * \ingroup engines + * + * \brief The asynchronous consistent engine executed vertex programs + * asynchronously and can ensure mutual exclusion such that adjacent vertices + * are never executed simultaneously. The default mode is "factorized" + * consistency in which only individual gathers/applys/ + * scatters are guaranteed to be consistent, but this can be strengthened to + * provide full mutual exclusion. + * + * + * \tparam VertexProgram + * The user defined vertex program type which should implement the + * \ref graphlab::ivertex_program interface. + * + * ### Execution Semantics + * + * On start() the \ref graphlab::ivertex_program::init function is invoked + * on all vertex programs in parallel to initialize the vertex program, + * vertex data, and possibly signal vertices. + * + * After which, the engine spawns a collection of threads where each thread + * individually performs the following tasks: + * \li Extract a message from the scheduler. + * \li Perform distributed lock acquisition on the vertex which is supposed + * to receive the message. The lock system enforces that no neighboring + * vertex is executing at the same time. The implementation is based + * on the Chandy-Misra solution to the dining philosophers problem. + * (Chandy, K.M.; Misra, J. (1984). The Drinking Philosophers Problem. + * ACM Trans. Program. Lang. Syst) + * \li Once lock acquisition is complete, + * \ref graphlab::ivertex_program::init is called on the vertex + * program. As an optimization, any messages sent to this vertex + * before completion of lock acquisition is merged into original message + * extracted from the scheduler. + * \li Execute the gather on the vertex program by invoking + * the user defined \ref graphlab::ivertex_program::gather function + * on the edge direction returned by the + * \ref graphlab::ivertex_program::gather_edges function. The gather + * functions can modify edge data but cannot modify the vertex + * program or vertex data and can be executed on multiple + * edges in parallel. + * * \li Execute the apply function on the vertex-program by + * invoking the user defined \ref graphlab::ivertex_program::apply + * function passing the sum of the gather functions. If \ref + * graphlab::ivertex_program::gather_edges returns no edges then + * the default gather value is passed to apply. The apply function + * can modify the vertex program and vertex data. + * \li Execute the scatter on the vertex program by invoking + * the user defined \ref graphlab::ivertex_program::scatter function + * on the edge direction returned by the + * \ref graphlab::ivertex_program::scatter_edges function. The scatter + * functions can modify edge data but cannot modify the vertex + * program or vertex data and can be executed on multiple + * edges in parallel. + * \li Release all locks acquired in the lock acquisition stage, + * and repeat until the scheduler is empty. + * + * The engine threads multiplexes the above procedure through a secondary + * internal queue, allowing an arbitrary large number of vertices to + * begin processing at the same time. + * + * ### Construction + * + * The asynchronous consistent engine is constructed by passing in a + * \ref graphlab::distributed_control object which manages coordination + * between engine threads and a \ref graphlab::distributed_graph object + * which is the graph on which the engine should be run. The graph should + * already be populated and cannot change after the engine is constructed. + * In the distributed setting all program instances (running on each machine) + * should construct an instance of the engine at the same time. + * + * Computation is initiated by signaling vertices using either + * \ref graphlab::powerlyra_async_engine::signal or + * \ref graphlab::powerlyra_async_engine::signal_all. In either case all + * machines should invoke signal or signal all at the same time. Finally, + * computation is initiated by calling the + * \ref graphlab::powerlyra_async_engine::start function. + * + * ### Example Usage + * + * The following is a simple example demonstrating how to use the engine: + * \code + * #include + * + * struct vertex_data { + * // code + * }; + * struct edge_data { + * // code + * }; + * typedef graphlab::distributed_graph graph_type; + * typedef float gather_type; + * struct pagerank_vprog : + * public graphlab::ivertex_program { + * // code + * }; + * + * int main(int argc, char** argv) { + * // Initialize control plain using mpi + * graphlab::mpi_tools::init(argc, argv); + * graphlab::distributed_control dc; + * // Parse command line options + * graphlab::command_line_options clopts("PageRank algorithm."); + * std::string graph_dir; + * clopts.attach_option("graph", &graph_dir, graph_dir, + * "The graph file."); + * if(!clopts.parse(argc, argv)) { + * std::cout << "Error in parsing arguments." << std::endl; + * return EXIT_FAILURE; + * } + * graph_type graph(dc, clopts); + * graph.load_structure(graph_dir, "tsv"); + * graph.finalize(); + * std::cout << "#vertices: " << graph.num_vertices() + * << " #edges:" << graph.num_edges() << std::endl; + * graphlab::powerlyra_async_engine engine(dc, graph, clopts); + * engine.signal_all(); + * engine.start(); + * std::cout << "Runtime: " << engine.elapsed_seconds(); + * graphlab::mpi_tools::finalize(); + * } + * \endcode + * + * \see graphlab::omni_engine + * \see graphlab::synchronous_engine + * + * Engine Options + * ========================= + * The asynchronous engine supports several engine options which can + * be set as command line arguments using \c --engine_opts : + * + * \li \b timeout (default: infinity) Maximum time in seconds the engine will + * run for. The actual runtime may be marginally greater as the engine + * waits for all threads and processes to flush all active tasks before + * returning. + * \li \b factorized (default: true) Set to true to weaken the consistency + * model to factorized consistency where only individual gather/apply/scatter + * calls are guaranteed to be locally consistent. Can produce massive + * increases in throughput at a consistency penalty. + * \li \b nfibers (default: 10000) Number of fibers to use + * \li \b stacksize (default: 16384) Stacksize of each fiber. + */ + template + class powerlyra_async_engine: public iengine { + + public: + /** + * \brief The user defined vertex program type. Equivalent to the + * VertexProgram template argument. + * + * The user defined vertex program type which should implement the + * \ref graphlab::ivertex_program interface. + */ + typedef VertexProgram vertex_program_type; + + /** + * \brief The user defined type returned by the gather function. + * + * The gather type is defined in the \ref graphlab::ivertex_program + * interface and is the value returned by the + * \ref graphlab::ivertex_program::gather function. The + * gather type must have an operator+=(const gather_type& + * other) function and must be \ref sec_serializable. + */ + typedef typename VertexProgram::gather_type gather_type; + + /** + * \brief The user defined message type used to signal neighboring + * vertex programs. + * + * The message type is defined in the \ref graphlab::ivertex_program + * interface and used in the call to \ref graphlab::icontext::signal. + * The message type must have an + * operator+=(const gather_type& other) function and + * must be \ref sec_serializable. + */ + typedef typename VertexProgram::message_type message_type; + + /** + * \brief The type of data associated with each vertex in the graph + * + * The vertex data type must be \ref sec_serializable. + */ + typedef typename VertexProgram::vertex_data_type vertex_data_type; + + /** + * \brief The type of data associated with each edge in the graph + * + * The edge data type must be \ref sec_serializable. + */ + typedef typename VertexProgram::edge_data_type edge_data_type; + + /** + * \brief The type of graph supported by this vertex program + * + * See graphlab::distributed_graph + */ + typedef typename VertexProgram::graph_type graph_type; + + /** + * \brief The type used to represent a vertex in the graph. + * See \ref graphlab::distributed_graph::vertex_type for details + * + * The vertex type contains the function + * \ref graphlab::distributed_graph::vertex_type::data which + * returns a reference to the vertex data as well as other functions + * like \ref graphlab::distributed_graph::vertex_type::num_in_edges + * which returns the number of in edges. + * + */ + typedef typename graph_type::vertex_type vertex_type; + + /** + * \brief The type used to represent an edge in the graph. + * See \ref graphlab::distributed_graph::edge_type for details. + * + * The edge type contains the function + * \ref graphlab::distributed_graph::edge_type::data which returns a + * reference to the edge data. In addition the edge type contains + * the function \ref graphlab::distributed_graph::edge_type::source and + * \ref graphlab::distributed_graph::edge_type::target. + * + */ + typedef typename graph_type::edge_type edge_type; + + /** + * \brief The type of the callback interface passed by the engine to vertex + * programs. See \ref graphlab::icontext for details. + * + * The context callback is passed to the vertex program functions and is + * used to signal other vertices, get the current iteration, and access + * information about the engine. + */ + typedef icontext icontext_type; + + private: + /// \internal \brief The base type of all schedulers + message_array messages; + + /** \internal + * \brief The true type of the callback context interface which + * implements icontext. \see graphlab::icontext graphlab::context + */ + typedef context context_type; + + // context needs access to internal functions + friend class context; + + /// \internal \brief The type used to refer to vertices in the local graph + typedef typename graph_type::local_vertex_type local_vertex_type; + /// \internal \brief The type used to refer to edges in the local graph + typedef typename graph_type::local_edge_type local_edge_type; + /// \internal \brief The type used to refer to vertex IDs in the local graph + typedef typename graph_type::lvid_type lvid_type; + + /// \internal \brief The type of the current engine instantiation + typedef powerlyra_async_engine engine_type; + + typedef conditional_addition_wrapper conditional_gather_type; + + /// The RPC interface + dc_dist_object > rmi; + + /// A reference to the active graph + graph_type& graph; + + /// A pointer to the lock implementation + distributed_chandy_misra* cmlocks; + + /// Per vertex data locks + std::vector vertexlocks; + + /// Total update function completion time + std::vector total_completion_time; + + /** + * \brief This optional vector contains caches of previous gather + * contributions for each machine. + * + * Caching is done locally and therefore a high-degree vertex may + * have multiple caches (one per machine). + */ + std::vector gather_cache; + + /** + * \brief A bit indicating if the local gather for that vertex is + * available. + */ + dense_bitset has_cache; + + bool use_cache; + + /// Engine threads. + fiber_group thrgroup; + + //! The scheduler + ischeduler* scheduler_ptr; + + typedef typename iengine::aggregator_type aggregator_type; + aggregator_type aggregator; + + /// Number of kernel threads + size_t ncpus; + /// Size of each fiber stack + size_t stacksize; + /// Number of fibers + size_t nfibers; + /// set to true if engine is started + bool started; + + bool track_task_time; + /// A pointer to the distributed consensus object + fiber_async_consensus* consensus; + + /** + * Used only by the locking subsystem. + * to allow the fiber to go to sleep when waiting for the locks to + * be ready. + */ + struct vertex_fiber_cm_handle { + mutex lock; + bool philosopher_ready; + size_t fiber_handle; + }; + std::vector cm_handles; + + dense_bitset program_running; + dense_bitset hasnext; + + // Various counters. + atomic programs_executed; + + timer launch_timer; + + /// Defaults to (-1), defines a timeout + size_t timed_termination; + + /// engine option. Sets to true if factorized consistency is used + bool factorized_consistency; + + bool endgame_mode; + + /// The number of try_to_quit + long nttqs; + + /// Time when engine is started + float engine_start_time; + + /// True when a force stop is triggered (possibly via a timeout) + bool force_stop; + + graphlab_options opts_copy; // local copy of options to pass to + // scheduler construction + + execution_status::status_enum termination_reason; + + std::vector aggregation_lock; + std::vector > aggregation_queue; + public: + + /** + * Constructs an asynchronous consistent distributed engine. + * The number of threads to create are read from + * \ref graphlab_options::get_ncpus "opts.get_ncpus()". The scheduler to + * construct is read from + * \ref graphlab_options::get_scheduler_type() "opts.get_scheduler_type()". + * The default scheduler + * is the queued_fifo scheduler. For details on the scheduler types + * \see scheduler_types + * + * See the main class documentation for the + * available engine options. + * + * \param dc Distributed controller to associate with + * \param graph The graph to schedule over. The graph must be fully + * constructed and finalized. + * \param opts A graphlab::graphlab_options object containing options and + * parameters for the scheduler and the engine. + */ + powerlyra_async_engine(distributed_control &dc, + graph_type& graph, + const graphlab_options& opts = graphlab_options()) : + rmi(dc, this), graph(graph), scheduler_ptr(NULL), + aggregator(dc, graph, new context_type(*this, graph)), started(false), + engine_start_time(timer::approx_time_seconds()), force_stop(false) { + rmi.barrier(); + + nfibers = 10000; + stacksize = 16384; + use_cache = false; + factorized_consistency = true; + track_task_time = false; + timed_termination = (size_t)(-1); + termination_reason = execution_status::UNSET; + set_options(opts); + init(); + total_completion_time.resize(fiber_control::get_instance().num_workers()); + init(); + rmi.barrier(); + } + + private: + + /** + * \internal + * Configures the engine with the provided options. + * The number of threads to create are read from + * opts::get_ncpus(). The scheduler to construct is read from + * graphlab_options::get_scheduler_type(). The default scheduler + * is the queued_fifo scheduler. For details on the scheduler types + * \see scheduler_types + */ + void set_options(const graphlab_options& opts) { + rmi.barrier(); + ncpus = opts.get_ncpus(); + ASSERT_GT(ncpus, 0); + aggregation_lock.resize(opts.get_ncpus()); + aggregation_queue.resize(opts.get_ncpus()); + std::vector keys = opts.get_engine_args().get_option_keys(); + foreach(std::string opt, keys) { + if (opt == "timeout") { + opts.get_engine_args().get_option("timeout", timed_termination); + if (rmi.procid() == 0) + logstream(LOG_EMPH) << "Engine Option: timeout = " << timed_termination << std::endl; + } else if (opt == "factorized") { + opts.get_engine_args().get_option("factorized", factorized_consistency); + if (rmi.procid() == 0) + logstream(LOG_EMPH) << "Engine Option: factorized = " << factorized_consistency << std::endl; + } else if (opt == "nfibers") { + opts.get_engine_args().get_option("nfibers", nfibers); + if (rmi.procid() == 0) + logstream(LOG_EMPH) << "Engine Option: nfibers = " << nfibers << std::endl; + } else if (opt == "track_task_time") { + opts.get_engine_args().get_option("track_task_time", track_task_time); + if (rmi.procid() == 0) + logstream(LOG_EMPH) << "Engine Option: track_task_time = " << track_task_time<< std::endl; + }else if (opt == "stacksize") { + opts.get_engine_args().get_option("stacksize", stacksize); + if (rmi.procid() == 0) + logstream(LOG_EMPH) << "Engine Option: stacksize= " << stacksize << std::endl; + } else if (opt == "use_cache") { + opts.get_engine_args().get_option("use_cache", use_cache); + if (rmi.procid() == 0) + logstream(LOG_EMPH) << "Engine Option: use_cache = " << use_cache << std::endl; + } else { + logstream(LOG_FATAL) << "Unexpected Engine Option: " << opt << std::endl; + } + } + opts_copy = opts; + // set a default scheduler if none + if (opts_copy.get_scheduler_type() == "") { + opts_copy.set_scheduler_type("queued_fifo"); + } + + // construct scheduler passing in the copy of the options from set_options + scheduler_ptr = scheduler_factory:: + new_scheduler(graph.num_local_vertices(), + opts_copy); + rmi.barrier(); + + // create initial fork arrangement based on the alternate vid mapping + if (factorized_consistency == false) { + cmlocks = new distributed_chandy_misra(rmi.dc(), graph, + boost::bind(&engine_type::lock_ready, this, _1)); + + } + else { + cmlocks = NULL; + } + + // construct the termination consensus object + consensus = new fiber_async_consensus(rmi.dc(), nfibers); + } + + /** + * \internal + * Initializes the engine with respect to the associated graph. + * This call will initialize all internal and scheduling datastructures. + * This function must be called prior to any signal function. + */ + void init() { + // construct all the required datastructures + // deinitialize performs the reverse + graph.finalize(); + scheduler_ptr->set_num_vertices(graph.num_local_vertices()); + messages.resize(graph.num_local_vertices()); + vertexlocks.resize(graph.num_local_vertices()); + program_running.resize(graph.num_local_vertices()); + hasnext.resize(graph.num_local_vertices()); + if (use_cache) { + gather_cache.resize(graph.num_local_vertices(), gather_type()); + has_cache.resize(graph.num_local_vertices()); + has_cache.clear(); + } + if (!factorized_consistency) { + cm_handles.resize(graph.num_local_vertices()); + } + rmi.barrier(); + } + + + + public: + ~powerlyra_async_engine() { + delete consensus; + delete cmlocks; + delete scheduler_ptr; + } + + + + + // documentation inherited from iengine + size_t num_updates() const { + return programs_executed.value; + } + + + + + + // documentation inherited from iengine + float elapsed_seconds() const { + return timer::approx_time_seconds() - engine_start_time; + } + + + /** + * \brief Not meaningful for the asynchronous engine. Returns -1. + */ + int iteration() const { return -1; } + + +/************************************************************************** + * Signaling Interface * + **************************************************************************/ + + private: + + /** + * \internal + * This is used to receive a message forwarded from another machine + */ + void rpc_signal(vertex_id_type vid, + const message_type& message) { + if (force_stop) return; + const lvid_type local_vid = graph.local_vid(vid); + double priority; + messages.add(local_vid, message, &priority); + scheduler_ptr->schedule(local_vid, priority); + consensus->cancel(); + } + + /** + * \internal + * \brief Signals a vertex with an optional message + * + * Signals a vertex, and schedules it to be executed in the future. + * must be called on a vertex accessible by the current machine. + */ + void internal_signal(const vertex_type& vtx, + const message_type& message = message_type()) { + if (force_stop) return; + if (started) { + const typename graph_type::vertex_record& rec = graph.l_get_vertex_record(vtx.local_id()); + const procid_t owner = rec.owner; + if (endgame_mode) { + // fast signal. push to the remote machine immediately + if (owner != rmi.procid()) { + const vertex_id_type vid = rec.gvid; + rmi.remote_call(owner, &engine_type::rpc_signal, vid, message); + } + else { + double priority; + messages.add(vtx.local_id(), message, &priority); + scheduler_ptr->schedule(vtx.local_id(), priority); + consensus->cancel(); + } + } + else { + + double priority; + messages.add(vtx.local_id(), message, &priority); + scheduler_ptr->schedule(vtx.local_id(), priority); + consensus->cancel(); + } + } + else { + double priority; + messages.add(vtx.local_id(), message, &priority); + scheduler_ptr->schedule(vtx.local_id(), priority); + consensus->cancel(); + } + } // end of schedule + + + /** + * \internal + * \brief Signals a vertex with an optional message + * + * Signals a global vid, and schedules it to be executed in the future. + * If current machine does not contain the vertex, it is ignored. + */ + void internal_signal_gvid(vertex_id_type gvid, + const message_type& message = message_type()) { + if (force_stop) return; + if (graph.is_master(gvid)) { + internal_signal(graph.vertex(gvid), message); + } else { + procid_t proc = graph.master(gvid); + rmi.remote_call(proc, &powerlyra_async_engine::internal_signal_gvid, + gvid, message); + } + } + + + void rpc_internal_stop() { + force_stop = true; + termination_reason = execution_status::FORCED_ABORT; + } + + /** + * \brief Force engine to terminate immediately. + * + * This function is used to stop the engine execution by forcing + * immediate termination. + */ + void internal_stop() { + for (procid_t i = 0;i < rmi.numprocs(); ++i) { + rmi.remote_call(i, &powerlyra_async_engine::rpc_internal_stop); + } + } + + + + /** + * \brief Post a to a previous gather for a give vertex. + * + * This function is called by the \ref graphlab::context. + * + * @param [in] vertex The vertex to which to post a change in the sum + * @param [in] delta The change in that sum + */ + void internal_post_delta(const vertex_type& vertex, + const gather_type& delta) { + if(use_cache) { + const lvid_type lvid = vertex.local_id(); + vertexlocks[lvid].lock(); + if( has_cache.get(lvid) ) { + gather_cache[lvid] += delta; + } else { + // You cannot add a delta to an empty cache. A complete + // gather must have been run. + // gather_cache[lvid] = delta; + // has_cache.set_bit(lvid); + } + vertexlocks[lvid].unlock(); + } + } + + /** + * \brief Clear the cached gather for a vertex if one is + * available. + * + * This function is called by the \ref graphlab::context. + * + * @param [in] vertex the vertex for which to clear the cache + */ + void internal_clear_gather_cache(const vertex_type& vertex) { + const lvid_type lvid = vertex.local_id(); + if(use_cache && has_cache.get(lvid)) { + vertexlocks[lvid].lock(); + gather_cache[lvid] = gather_type(); + has_cache.clear_bit(lvid); + vertexlocks[lvid].unlock(); + } + + } + + public: + + + + void signal(vertex_id_type gvid, + const message_type& message = message_type()) { + rmi.barrier(); + internal_signal_gvid(gvid, message); + rmi.barrier(); + } + + + void signal_all(const message_type& message = message_type(), + const std::string& order = "shuffle") { + vertex_set vset = graph.complete_set(); + signal_vset(vset, message, order); + } // end of schedule all + + void signal_vset(const vertex_set& vset, + const message_type& message = message_type(), + const std::string& order = "shuffle") { + logstream(LOG_DEBUG) << rmi.procid() << ": Schedule All" << std::endl; + // allocate a vector with all the local owned vertices + // and schedule all of them. + std::vector vtxs; + vtxs.reserve(graph.num_local_own_vertices()); + for(lvid_type lvid = 0; + lvid < graph.get_local_graph().num_vertices(); + ++lvid) { + if (graph.l_vertex(lvid).owner() == rmi.procid() && + vset.l_contains(lvid)) { + vtxs.push_back(lvid); + } + } + + if(order == "shuffle") { + graphlab::random::shuffle(vtxs.begin(), vtxs.end()); + } + foreach(lvid_type lvid, vtxs) { + double priority; + messages.add(lvid, message, &priority); + scheduler_ptr->schedule(lvid, priority); + } + rmi.barrier(); + } + + + private: + + /** + * Gets a task from the scheduler and the associated message + */ + sched_status::status_enum get_next_sched_task( size_t threadid, + lvid_type& lvid, + message_type& msg) { + while (1) { + sched_status::status_enum stat = + scheduler_ptr->get_next(threadid % ncpus, lvid); + if (stat == sched_status::NEW_TASK) { + if (messages.get(lvid, msg)) return stat; + else continue; + } + return stat; + } + } + + void set_endgame_mode() { + if (!endgame_mode) logstream(LOG_EMPH) << "Endgame mode\n"; + endgame_mode = true; + rmi.dc().set_fast_track_requests(true); + } + + /** + * \internal + * Called when get_a_task returns no internal task not a scheduler task. + * This rechecks the status of the internal task queue and the scheduler + * inside a consensus critical section. + */ + bool try_to_quit(size_t threadid, + bool& has_sched_msg, + lvid_type& sched_lvid, + message_type &msg) { + if (timer::approx_time_seconds() - engine_start_time > timed_termination) { + termination_reason = execution_status::TIMEOUT; + force_stop = true; + } + fiber_control::yield(); + + nttqs ++; + logstream(LOG_DEBUG) << rmi.procid() << "-" << threadid << ": " << "Termination Attempt " << std::endl; + has_sched_msg = false; + consensus->begin_done_critical_section(threadid); + sched_status::status_enum stat = + get_next_sched_task(threadid, sched_lvid, msg); + if (stat == sched_status::EMPTY || force_stop) { + logstream(LOG_DEBUG) << rmi.procid() << "-" << threadid << ": " + << "\tTermination Double Checked" << std::endl; + + if (!endgame_mode) + logstream(LOG_EMPH) << rmi.procid() << " Endgame mode " + << (timer::approx_time_seconds() - engine_start_time) + << std::endl; + endgame_mode = true; + // put everyone in endgame + for (procid_t i = 0;i < rmi.dc().numprocs(); ++i) { + rmi.remote_call(i, &powerlyra_async_engine::set_endgame_mode); + } + bool ret = consensus->end_done_critical_section(threadid); + if (ret == false) { + logstream(LOG_DEBUG) << rmi.procid() << "-" << threadid << ": " + << "\tCancelled" << std::endl; + } else { + logstream(LOG_DEBUG) << rmi.procid() << "-" << threadid << ": " + << "\tDying" << " (" << fiber_control::get_tid() << ")" << std::endl; + } + return ret; + } else { + logstream(LOG_DEBUG) << rmi.procid() << "-" << threadid << ": " + << "\tCancelled by Scheduler Task" << std::endl; + consensus->cancel_critical_section(threadid); + has_sched_msg = true; + return false; + } + } // end of try to quit + + + /** + * \internal + * When all distributed locks are acquired, this function is called + * from the chandy misra implementation on the master vertex. + * Here, we perform initialization + * of the task and switch the vertex to a gathering state + */ + void lock_ready(lvid_type lvid) { + cm_handles[lvid]->lock.lock(); + cm_handles[lvid]->philosopher_ready = true; + fiber_control::schedule_tid(cm_handles[lvid]->fiber_handle); + cm_handles[lvid]->lock.unlock(); + } + + + conditional_gather_type perform_gather(vertex_id_type vid, + vertex_program_type& vprog_) { + vertex_program_type vprog = vprog_; + lvid_type lvid = graph.local_vid(vid); + local_vertex_type local_vertex(graph.l_vertex(lvid)); + vertex_type vertex(local_vertex); + context_type context(*this, graph); + edge_dir_type gather_dir = vprog.gather_edges(context, vertex); + conditional_gather_type accum; + + //check against the cache + if( use_cache && has_cache.get(lvid) ) { + accum.set(gather_cache[lvid]); + return accum; + } + // do in edges + if(gather_dir == IN_EDGES || gather_dir == ALL_EDGES) { + foreach(local_edge_type local_edge, local_vertex.in_edges()) { + edge_type edge(local_edge); + lvid_type a = edge.source().local_id(), b = edge.target().local_id(); + vertexlocks[std::min(a,b)].lock(); + vertexlocks[std::max(a,b)].lock(); + accum += vprog.gather(context, vertex, edge); + vertexlocks[a].unlock(); + vertexlocks[b].unlock(); + } + } + // do out edges + if(gather_dir == OUT_EDGES || gather_dir == ALL_EDGES) { + foreach(local_edge_type local_edge, local_vertex.out_edges()) { + edge_type edge(local_edge); + lvid_type a = edge.source().local_id(), b = edge.target().local_id(); + vertexlocks[std::min(a,b)].lock(); + vertexlocks[std::max(a,b)].lock(); + accum += vprog.gather(context, vertex, edge); + vertexlocks[a].unlock(); + vertexlocks[b].unlock(); + } + } + if (use_cache) { + gather_cache[lvid] = accum.value; has_cache.set_bit(lvid); + } + return accum; + } + + + void perform_scatter_local(lvid_type lvid, + vertex_program_type& vprog) { + local_vertex_type local_vertex(graph.l_vertex(lvid)); + vertex_type vertex(local_vertex); + context_type context(*this, graph); + edge_dir_type scatter_dir = vprog.scatter_edges(context, vertex); + if(scatter_dir == IN_EDGES || scatter_dir == ALL_EDGES) { + foreach(local_edge_type local_edge, local_vertex.in_edges()) { + edge_type edge(local_edge); + lvid_type a = edge.source().local_id(), b = edge.target().local_id(); + vertexlocks[std::min(a,b)].lock(); + vertexlocks[std::max(a,b)].lock(); + vprog.scatter(context, vertex, edge); + vertexlocks[a].unlock(); + vertexlocks[b].unlock(); + } + } + if(scatter_dir == OUT_EDGES || scatter_dir == ALL_EDGES) { + foreach(local_edge_type local_edge, local_vertex.out_edges()) { + edge_type edge(local_edge); + lvid_type a = edge.source().local_id(), b = edge.target().local_id(); + vertexlocks[std::min(a,b)].lock(); + vertexlocks[std::max(a,b)].lock(); + vprog.scatter(context, vertex, edge); + vertexlocks[a].unlock(); + vertexlocks[b].unlock(); + } + } + + // release locks + if (!factorized_consistency) { + cmlocks->philosopher_stops_eating_per_replica(lvid); + } + } + + + void perform_scatter(vertex_id_type vid, + vertex_program_type& vprog_, + const vertex_data_type& newdata) { + vertex_program_type vprog = vprog_; + lvid_type lvid = graph.local_vid(vid); + vertexlocks[lvid].lock(); + graph.l_vertex(lvid).data() = newdata; + vertexlocks[lvid].unlock(); + perform_scatter_local(lvid, vprog); + } + + + // make sure I am the only person running. + // if returns false, the message has been dropped into the message array. + // quit + bool get_exclusive_access_to_vertex(const lvid_type lvid, + const message_type& msg) { + vertexlocks[lvid].lock(); + bool someone_else_running = program_running.set_bit(lvid); + if (someone_else_running) { + // bad. someone else is here. + // drop it into the message array + messages.add(lvid, msg); + hasnext.set_bit(lvid); + } + vertexlocks[lvid].unlock(); + return !someone_else_running; + } + + + + // make sure I am the only person running. + // if returns false, the message has been dropped into the message array. + // quit + void release_exclusive_access_to_vertex(const lvid_type lvid) { + vertexlocks[lvid].lock(); + // someone left a next message for me + // reschedule it at high priority + if (hasnext.get(lvid)) { + scheduler_ptr->schedule(lvid, 10000.0); + consensus->cancel(); + hasnext.clear_bit(lvid); + } + program_running.clear_bit(lvid); + vertexlocks[lvid].unlock(); + } + + bool high_lvid(const lvid_type lvid) { + return graph.l_degree_type(lvid) == graph_type::HIGH; + } + + /** + * \internal + * Called when the scheduler returns a vertex to run. + * If this function is called with vertex locks acquired, prelocked + * should be true. Otherwise it should be false. + */ + void eval_sched_task(const lvid_type lvid, + const message_type& msg) { + const typename graph_type::vertex_record& rec = graph.l_get_vertex_record(lvid); + vertex_id_type vid = rec.gvid; + char task_time_data[sizeof(timer)]; + timer* task_time = NULL; + if (track_task_time) { + // placement new to create the timer + task_time = reinterpret_cast(task_time_data); + new (task_time) timer(); + } + // if this is another machine's forward it + if (rec.owner != rmi.procid()) { + rmi.remote_call(rec.owner, &engine_type::rpc_signal, vid, msg); + return; + } + // I have to run this myself + + if (!get_exclusive_access_to_vertex(lvid, msg)) return; + + /**************************************************************************/ + /* Acquire Locks */ + /**************************************************************************/ + if (!factorized_consistency) { + // begin lock acquisition + cm_handles[lvid] = new vertex_fiber_cm_handle; + cm_handles[lvid]->philosopher_ready = false; + cm_handles[lvid]->fiber_handle = fiber_control::get_tid(); + cmlocks->make_philosopher_hungry(lvid); + cm_handles[lvid]->lock.lock(); + while (!cm_handles[lvid]->philosopher_ready) { + fiber_control::deschedule_self(&(cm_handles[lvid]->lock.m_mut)); + cm_handles[lvid]->lock.lock(); + } + cm_handles[lvid]->lock.unlock(); + } + + /**************************************************************************/ + /* Begin Program */ + /**************************************************************************/ + context_type context(*this, graph); + vertex_program_type vprog = vertex_program_type(); + local_vertex_type local_vertex(graph.l_vertex(lvid)); + vertex_type vertex(local_vertex); + bool high = high_lvid(lvid); + + /**************************************************************************/ + /* init phase */ + /**************************************************************************/ + vprog.init(context, vertex, msg); + + /**************************************************************************/ + /* Gather Phase */ + /**************************************************************************/ + conditional_gather_type gather_result; + std::vector > gather_futures; + edge_dir_type gather_dir = vprog.gather_edges(context, vertex); + + if (high || (gather_dir == graphlab::ALL_EDGES) + || (gather_dir == graphlab::OUT_EDGES)) { + foreach(procid_t mirror, local_vertex.mirrors()) { + gather_futures.push_back( + object_fiber_remote_request(rmi, + mirror, + &powerlyra_async_engine::perform_gather, + vid, + vprog)); + } + } + gather_result += perform_gather(vid, vprog); + if (high || (gather_dir == graphlab::ALL_EDGES) + || (gather_dir == graphlab::OUT_EDGES)) { + for(size_t i = 0;i < gather_futures.size(); ++i) { + gather_result += gather_futures[i](); + } + } + + /**************************************************************************/ + /* apply phase */ + /**************************************************************************/ + vertexlocks[lvid].lock(); + vprog.apply(context, vertex, gather_result.value); + vertexlocks[lvid].unlock(); + + + /**************************************************************************/ + /* scatter phase */ + /**************************************************************************/ + + // should I wait for the scatter? nah... but in case you want to + // the code is commented below + /*foreach(procid_t mirror, local_vertex.mirrors()) { + rmi.remote_call(mirror, + &powerlyra_async_engine::perform_scatter, + vid, + vprog, + local_vertex.data()); + }*/ + + std::vector > scatter_futures; + foreach(procid_t mirror, local_vertex.mirrors()) { + scatter_futures.push_back( + object_fiber_remote_request(rmi, + mirror, + &powerlyra_async_engine::perform_scatter, + vid, + vprog, + local_vertex.data())); + } + perform_scatter_local(lvid, vprog); + for(size_t i = 0;i < scatter_futures.size(); ++i) + scatter_futures[i](); + + /************************************************************************/ + /* Release Locks */ + /************************************************************************/ + // the scatter is used to release the chandy misra + // here I cleanup + if (!factorized_consistency) { + delete cm_handles[lvid]; + cm_handles[lvid] = NULL; + } + release_exclusive_access_to_vertex(lvid); + if (track_task_time) { + total_completion_time[fiber_control::get_worker_id()] += + task_time->current_time(); + task_time->~timer(); + } + programs_executed.inc(); + } + + + /** + * \internal + * Per thread main loop + */ + void thread_start(size_t threadid) { + bool has_sched_msg = false; + std::vector > internal_lvid; + lvid_type sched_lvid; + + message_type msg; + float last_aggregator_check = timer::approx_time_seconds(); + timer ti; ti.start(); + while(1) { + if (timer::approx_time_seconds() != last_aggregator_check && !endgame_mode) { + last_aggregator_check = timer::approx_time_seconds(); + std::string key = aggregator.tick_asynchronous(); + if (key != "") { + for (size_t i = 0;i < aggregation_lock.size(); ++i) { + aggregation_lock[i].lock(); + aggregation_queue[i].push_back(key); + aggregation_lock[i].unlock(); + } + } + } + + // test the aggregator + while(!aggregation_queue[fiber_control::get_worker_id()].empty()) { + size_t wid = fiber_control::get_worker_id(); + ASSERT_LT(wid, ncpus); + aggregation_lock[wid].lock(); + std::string key = aggregation_queue[wid].front(); + aggregation_queue[wid].pop_front(); + aggregation_lock[wid].unlock(); + aggregator.tick_asynchronous_compute(wid, key); + } + + sched_status::status_enum stat = get_next_sched_task(threadid, sched_lvid, msg); + + + has_sched_msg = stat != sched_status::EMPTY; + if (stat != sched_status::EMPTY) { + eval_sched_task(sched_lvid, msg); + if (endgame_mode) rmi.dc().flush(); + } + else if (!try_to_quit(threadid, has_sched_msg, sched_lvid, msg)) { + /* + * We failed to obtain a task, try to quit + */ + if (has_sched_msg) { + eval_sched_task(sched_lvid, msg); + } + } else { + break; + } + + if (fiber_control::worker_has_priority_fibers_on_queue()) { + fiber_control::yield(); + } + } + } // end of thread start + +/************************************************************************** + * Main engine start() * + **************************************************************************/ + + public: + + + /** + * \brief Start the engine execution. + * + * This function starts the engine and does not + * return until the scheduler has no tasks remaining. + * + * \return the reason for termination + */ + execution_status::status_enum start() { + bool old_fasttrack = rmi.dc().set_fast_track_requests(false); + logstream(LOG_INFO) << "Spawning " << nfibers << " threads" << std::endl; + ASSERT_TRUE(scheduler_ptr != NULL); + consensus->reset(); + + // now. It is of critical importance that we match the number of + // actual workers + + + // start the aggregator + aggregator.start(ncpus); + aggregator.aggregate_all_periodic(); + + started = true; + + rmi.barrier(); + size_t allocatedmem = memory_info::allocated_bytes(); + rmi.all_reduce(allocatedmem); + + engine_start_time = timer::approx_time_seconds(); + force_stop = false; + endgame_mode = false; + programs_executed = 0; + nttqs = 0; + launch_timer.start(); + + termination_reason = execution_status::RUNNING; + if (rmi.procid() == 0) { + logstream(LOG_INFO) << "Total Allocated Bytes: " << allocatedmem << std::endl; + } + thrgroup.set_stacksize(stacksize); + + size_t effncpus = std::min(ncpus, fiber_control::get_instance().num_workers()); + for (size_t i = 0; i < nfibers ; ++i) { + thrgroup.launch(boost::bind(&engine_type::thread_start, this, i), + i % effncpus); + } + thrgroup.join(); + aggregator.stop(); + // if termination reason was not changed, then it must be depletion + if (termination_reason == execution_status::RUNNING) { + termination_reason = execution_status::TASK_DEPLETION; + } + + size_t ctasks = programs_executed.value; + rmi.all_reduce(ctasks); + programs_executed.value = ctasks; + + logstream(LOG_INFO) << rmi.procid() << " #try_to_quit = " << nttqs + << std::endl; + + rmi.cout() << "Completed Tasks: " << programs_executed.value << std::endl; + + + size_t numjoins = messages.num_joins(); + rmi.all_reduce(numjoins); + rmi.cout() << "Schedule Joins: " << numjoins << std::endl; + + size_t numadds = messages.num_adds(); + rmi.all_reduce(numadds); + rmi.cout() << "Schedule Adds: " << numadds << std::endl; + + if (track_task_time) { + double total_task_time = 0; + for (size_t i = 0;i < total_completion_time.size(); ++i) { + total_task_time += total_completion_time[i]; + } + rmi.all_reduce(total_task_time); + rmi.cerr() << "Average Task Completion Time = " + << total_task_time / programs_executed.value << std::endl; + } + + + ASSERT_TRUE(scheduler_ptr->empty()); + started = false; + + rmi.dc().set_fast_track_requests(old_fasttrack); + return termination_reason; + } // end of start + + + public: + aggregator_type* get_aggregator() { return &aggregator; } + + }; // end of class +} // namespace + +#include + +#endif // GRAPHLAB_DISTRIBUTED_ENGINE_HPP + diff --git a/src/graphlab/rpc/fiber_async_consensus.hpp b/src/graphlab/rpc/fiber_async_consensus.hpp index 9b4c3de4a1..9af2fda1b3 100644 --- a/src/graphlab/rpc/fiber_async_consensus.hpp +++ b/src/graphlab/rpc/fiber_async_consensus.hpp @@ -80,7 +80,7 @@ namespace graphlab { * \endcode * * Additionally, incoming RPC calls which create work must ensure there are - * active fiberswhich are capable of processing the work. An easy solution + * active fibers which are capable of processing the work. An easy solution * will be to simply cancel_one(). Other more optimized solutions * include keeping a counter of the number of active fibers, and only calling * cancel() or cancel_one() if all fibers are asleep. (Note that the optimized From 53a376e0d7ca3f530943b8e2dcff845be7d90f95 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Mon, 24 Mar 2014 22:16:07 +0800 Subject: [PATCH 34/50] add more output info --- src/graphlab/engine/powerlyra_sync_engine.hpp | 2 +- src/graphlab/scheduler/fifo_scheduler.cpp | 4 ++ src/graphlab/scheduler/priority_scheduler.cpp | 4 ++ .../scheduler/queued_fifo_scheduler.cpp | 5 ++ src/graphlab/scheduler/sweep_scheduler.cpp | 7 +++ toolkits/graph_analytics/pagerank.cpp | 4 +- toolkits/graph_analytics/sssp.cpp | 53 +++++++++++++++---- 7 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/graphlab/engine/powerlyra_sync_engine.hpp b/src/graphlab/engine/powerlyra_sync_engine.hpp index 44fb43fa10..d807b579d5 100644 --- a/src/graphlab/engine/powerlyra_sync_engine.hpp +++ b/src/graphlab/engine/powerlyra_sync_engine.hpp @@ -56,7 +56,7 @@ #include -#undef TUNING +#define TUNING namespace graphlab { diff --git a/src/graphlab/scheduler/fifo_scheduler.cpp b/src/graphlab/scheduler/fifo_scheduler.cpp index 1896258924..30b64f2854 100644 --- a/src/graphlab/scheduler/fifo_scheduler.cpp +++ b/src/graphlab/scheduler/fifo_scheduler.cpp @@ -53,6 +53,10 @@ fifo_scheduler::fifo_scheduler(size_t num_vertices, ASSERT_GE(opts.get_ncpus(), 1); set_options(opts); initialize_data_structures(); + logstream(LOG_INFO) << "FIFO Scheduler:" + << " multi=" << multi + << std::endl; + } diff --git a/src/graphlab/scheduler/priority_scheduler.cpp b/src/graphlab/scheduler/priority_scheduler.cpp index b0e0ec532e..29294e2f57 100644 --- a/src/graphlab/scheduler/priority_scheduler.cpp +++ b/src/graphlab/scheduler/priority_scheduler.cpp @@ -56,6 +56,10 @@ priority_scheduler::priority_scheduler(size_t num_vertices, ASSERT_GE(opts.get_ncpus(), 1); set_options(opts); initialize_data_structures(); + logstream(LOG_INFO) << "Priority Scheduler:" + << " min_priority=" << min_priority + << " multi=" << multi + << std::endl; } diff --git a/src/graphlab/scheduler/queued_fifo_scheduler.cpp b/src/graphlab/scheduler/queued_fifo_scheduler.cpp index 7ce8288e72..ebad6dae66 100644 --- a/src/graphlab/scheduler/queued_fifo_scheduler.cpp +++ b/src/graphlab/scheduler/queued_fifo_scheduler.cpp @@ -58,6 +58,11 @@ queued_fifo_scheduler::queued_fifo_scheduler(size_t num_vertices, ASSERT_GE(opts.get_ncpus(), 1); set_options(opts); initialize_data_structures(); + + logstream(LOG_INFO) << "Queued-FIFO Scheduler:" + << " queuesize=" << sub_queue_size + << " multi=" << multi + << std::endl; } void queued_fifo_scheduler::set_num_vertices(const lvid_type numv) { diff --git a/src/graphlab/scheduler/sweep_scheduler.cpp b/src/graphlab/scheduler/sweep_scheduler.cpp index 33ba7a258b..f8e2651fb0 100644 --- a/src/graphlab/scheduler/sweep_scheduler.cpp +++ b/src/graphlab/scheduler/sweep_scheduler.cpp @@ -82,6 +82,13 @@ sweep_scheduler::sweep_scheduler(size_t num_vertices, for(size_t i = 0; i < cpu2index.size(); ++i) cpu2index[i] = i; } vertex_is_scheduled.resize(num_vertices); + + logstream(LOG_INFO) << "Sweep Scheduler:" + << " order=" << ordering + << " strict=" << strict_round_robin + << " max_iterations=" << max_iterations + << std::endl; + } // end of constructor diff --git a/toolkits/graph_analytics/pagerank.cpp b/toolkits/graph_analytics/pagerank.cpp index cdc55343be..4a63c14128 100644 --- a/toolkits/graph_analytics/pagerank.cpp +++ b/toolkits/graph_analytics/pagerank.cpp @@ -240,13 +240,13 @@ int main(int argc, char** argv) { return 0; } dc.cout() << "Loading graph. Finished in " - << timer.current_time() << std::endl; + << timer.current_time() << std::endl; // must call finalize before querying the graph dc.cout() << "Finalizing graph." << std::endl; timer.start(); graph.finalize(); dc.cout() << "Finalizing graph. Finished in " - << timer.current_time() << std::endl; + << timer.current_time() << std::endl; dc.cout() << "#vertices: " << graph.num_vertices() << " #edges:" << graph.num_edges() << std::endl; diff --git a/toolkits/graph_analytics/sssp.cpp b/toolkits/graph_analytics/sssp.cpp index 59eed0d4f8..66367db984 100644 --- a/toolkits/graph_analytics/sssp.cpp +++ b/toolkits/graph_analytics/sssp.cpp @@ -100,6 +100,17 @@ struct min_distance_type : graphlab::IS_POD_TYPE { } }; +struct max_distance_type : graphlab::IS_POD_TYPE { + distance_type dist; + max_distance_type(distance_type dist = + std::numeric_limits::min()) : dist(dist) { } + max_distance_type& operator+=(const max_distance_type& other) { + dist = std::max(dist, other.dist); + return *this; + } +}; + + /** * \brief The single source shortest path vertex program. @@ -192,7 +203,6 @@ struct shortest_path_writer { }; // end of shortest_path_writer - struct max_deg_vertex_reducer: public graphlab::IS_POD_TYPE { size_t degree; graphlab::vertex_id_type vid; @@ -211,6 +221,14 @@ max_deg_vertex_reducer find_max_deg_vertex(const graph_type::vertex_type vtx) { return red; } +max_distance_type map_dist(const graph_type::vertex_type& v) { + if (v.data().dist == std::numeric_limits::max()) + return std::numeric_limits::min(); + + max_distance_type dist(v.data().dist); + return dist; +} + int main(int argc, char** argv) { // Initialize control plain using mpi graphlab::mpi_tools::init(argc, argv); @@ -260,6 +278,8 @@ int main(int argc, char** argv) { // Build the graph ---------------------------------------------------------- + dc.cout() << "Loading graph." << std::endl; + graphlab::timer timer; graph_type graph(dc, clopts); if(powerlaw > 0) { // make a synthetic graph dc.cout() << "Loading synthetic Powerlaw graph." << std::endl; @@ -272,12 +292,17 @@ int main(int argc, char** argv) { clopts.print_description(); return EXIT_FAILURE; } + dc.cout() << "Loading graph. Finished in " + << timer.current_time() << std::endl; // must call finalize before querying the graph + dc.cout() << "Finalizing graph." << std::endl; + timer.start(); graph.finalize(); - dc.cout() << "#vertices: " << graph.num_vertices() << std::endl - << "#edges: " << graph.num_edges() << std::endl; - + dc.cout() << "Finalizing graph. Finished in " + << timer.current_time() << std::endl; + dc.cout() << "#vertices: " << graph.num_vertices() + << " #edges:" << graph.num_edges() << std::endl; if(sources.empty()) { if (max_degree_source == false) { @@ -297,22 +322,28 @@ int main(int argc, char** argv) { } - // Running The Engine ------------------------------------------------------- graphlab::omni_engine engine(dc, graph, exec_type, clopts); - - // Signal all the vertices in the source set for(size_t i = 0; i < sources.size(); ++i) { engine.signal(sources[i], min_distance_type(0)); } + timer.start(); engine.start(); - const float runtime = engine.elapsed_seconds(); - dc.cout() << "Finished Running engine in " << runtime - << " seconds." << std::endl; - + const double runtime = timer.current_time(); + dc.cout() << "----------------------------------------------------------" + << std::endl + << "Final Runtime (seconds): " << runtime + << std::endl + << "Updates executed: " << engine.num_updates() << std::endl + << "Update Rate (updates/second): " + << engine.num_updates() / runtime << std::endl; + + const max_distance_type max_dist = + graph.map_reduce_vertices(map_dist); + std::cout << "Max distance: " << max_dist.dist << std::endl; // Save the final graph ----------------------------------------------------- if (saveprefix != "") { From 298888d90dad58dd5c542f651f904d3ef3c53b83 Mon Sep 17 00:00:00 2001 From: "Rong.Chen" Date: Mon, 24 Mar 2014 22:21:57 +0800 Subject: [PATCH 35/50] wrong size of out_queue_locks in queued_fifo_scheduler.cpp --- src/graphlab/scheduler/queued_fifo_scheduler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphlab/scheduler/queued_fifo_scheduler.cpp b/src/graphlab/scheduler/queued_fifo_scheduler.cpp index ebad6dae66..bf8f1949be 100644 --- a/src/graphlab/scheduler/queued_fifo_scheduler.cpp +++ b/src/graphlab/scheduler/queued_fifo_scheduler.cpp @@ -44,7 +44,7 @@ void queued_fifo_scheduler::initialize_data_structures() { ASSERT_GT(ncpus * multi, 1); in_queues.resize(ncpus * multi); in_queue_locks.resize(ncpus * multi); - out_queue_locks.resize(ncpus * multi); + out_queue_locks.resize(ncpus); out_queues.resize(ncpus); vertex_is_scheduled.resize(num_vertices); } From 6fa3c94e7eab47261ab77cc54a9452ebf9e86595 Mon Sep 17 00:00:00 2001 From: Chen Rong Date: Wed, 2 Apr 2014 09:51:04 +0800 Subject: [PATCH 36/50] typo --- src/graphlab/graph/distributed_graph.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphlab/graph/distributed_graph.hpp b/src/graphlab/graph/distributed_graph.hpp index 0e17c08d24..e34a699b3b 100644 --- a/src/graphlab/graph/distributed_graph.hpp +++ b/src/graphlab/graph/distributed_graph.hpp @@ -420,7 +420,7 @@ namespace graphlab { typedef graphlab::lvid_type lvid_type; typedef graphlab::edge_id_type edge_id_type; - enum degree_type {HIGH = 0, LOW, NUM_ZONE_TYPES}; + enum degree_type {HIGH = 0, LOW, NUM_DEGREE_TYPES}; enum cuts_type {VERTEX_CUTS = 0, EDGE_CUTS, HYBRID_CUTS, HYBRID_GINGER_CUTS, NUM_CUTS_TYPES}; From d3d1864df1ab5341d2ecde38f408f94592f50c80 Mon Sep 17 00:00:00 2001 From: Chen Rong Date: Wed, 3 Jun 2015 21:02:25 +0800 Subject: [PATCH 37/50] fix download link --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d2caafcee..f5d60b282d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -236,9 +236,9 @@ if(NOT NO_TCMALLOC) # We use tcmalloc for improved memory allocation performance ExternalProject_Add(libtcmalloc PREFIX ${GraphLab_SOURCE_DIR}/deps/tcmalloc - # Some users can't access domain googlecode.com ,This is a spare URL - # URL http://sourceforge.jp/projects/sfnet_gperftools.mirror/downloads/gperftools-2.0.tar.gz - URL http://gperftools.googlecode.com/files/gperftools-2.0.tar.gz + # Some users can't access domain googlecode.com, so we replace it with a link to our project server + # URL http://gperftools.googlecode.com/files/gperftools-2.0.tar.gz + URL http://ipads.se.sjtu.edu.cn/projects/powerlyra/deps/gperftools-2.0.tar.gz URL_MD5 13f6e8961bc6a26749783137995786b6 PATCH_COMMAND patch -N -p0 -i ${GraphLab_SOURCE_DIR}/patches/tcmalloc.patch || true CONFIGURE_COMMAND /configure --enable-frame-pointers --prefix= ${tcmalloc_shared} From c4a5f73e3eed3960e1c96194db1dede44965e6b1 Mon Sep 17 00:00:00 2001 From: Chen Rong Date: Tue, 9 Jun 2015 15:37:54 +0800 Subject: [PATCH 38/50] update readme --- BINARY_README | 41 ++++++ README.md | 273 ++++-------------------------------- README_graphlab.md | 269 +++++++++++++++++++++++++++++++++++ license/LICENSE_prepend.txt | 22 +++ 4 files changed, 359 insertions(+), 246 deletions(-) create mode 100644 README_graphlab.md diff --git a/BINARY_README b/BINARY_README index ed431bd515..67c581a542 100644 --- a/BINARY_README +++ b/BINARY_README @@ -1,4 +1,45 @@ + PowerLyra Binary Release + ------------------------ + +======= +License +======= + +PowerLyra is free software licensed under the Apache 2.0 License. See +license/LICENSE.txt for details. + +============ +Introduction +============ + +PowerLyra is based on the latest codebase of GraphLab PowerGraph (a distributed graph +computation framework written in C++) and can seamlessly support all GraphLab toolkits. + +PowerLyra Features: + +Hybrid computation engine: Exploit the locality of low-degree vertices + and the parallelism of high-degree vertices + +Hybrid partitioning algorithm: Differentiate the partitioning algorithms + for different types of vertices + +Diverse scheduling strategy: Provide both synchronous and asynchronous + computation engines + +Compatible API: Seamlessly support all GraphLab toolkits + + +====================== +Installation and Usage +====================== + +The installation and tutorial of PowerLyra fully follow that of GraphLab. +See following notes for details. + + + + Graphlab Binary Release ----------------------- diff --git a/README.md b/README.md index 5e02a54c01..bb95bc3f0f 100644 --- a/README.md +++ b/README.md @@ -1,269 +1,50 @@ -# GraphLab PowerGraph v2.2 - -## UPDATE: For a signficant evolution of this codebase, see GraphLab Create which is available for download at [dato.com](http://dato.com) - -## History -In 2013, the team that created GraphLab PowerGraph started the Seattle-based company, GraphLab, Inc. The learnings from GraphLab PowerGraph and GraphChi projects have culminated into GraphLab Create, a enterprise-class data science platform for data scientists and software engineers that can simplify building and deploying advanced machine learning models as a RESTful predictive service. In January 2015, GraphLab, Inc. was renamed to Dato, Inc. See [dato.com](http://dato.com) for more information. - -## Status -GraphLab PowerGraph is no longer in active development by the founding team. GraphLab PowerGraph is now supported by the community at [http://forum.dato.com/](http://forum.dato.com/). - -# Introduction - -GraphLab PowerGraph is a graph-based, high performance, distributed computation framework written in C++. - -The GraphLab PowerGraph academic project was started in 2009 at Carnegie Mellon University to develop a new parallel computation abstraction tailored to machine learning. GraphLab PowerGraph 1.0 employed shared-memory design. In GraphLab PowerGraph 2.1, the framework was redesigned to target the distributed environment. It addressed the difficulties with real-world power-law graphs and achieved unparalleled performance at the time. In GraphLab PowerGraph 2.2, the Warp System was introduced and provided a new flexible, distributed architecture around fine-grained user-mode threading (fibers). The Warp System allows one to easily extend the abstraction, to improve optimization for example, while also improving usability. - -GraphLab PowerGraph is the culmination of 4-years of research and development into graph computation, distributed computing, and machine learning. GraphLab PowerGraph scales to graphs with billions of vertices and edges easily, performing orders of magnitude faster than competing systems. GraphLab PowerGraph combines advances in machine learning algorithms, asynchronous distributed graph computation, prioritized scheduling, and graph placement with optimized low-level system design and efficient data-structures to achieve unmatched performance and scalability in challenging machine learning tasks. - -Related is GraphChi, a spin-off project separate from the GraphLab PowerGraph project. GraphChi was designed to run very large graph computations on just a single machine, by using a novel algorithm for processing the graph from disk (SSD or hard drive) enabling a single desktop computer (actually a Mac Mini) to tackle problems that previously demanded an entire cluster. For more information, see [https://github.com/GraphChi](https://github.com/GraphChi). - -# License - - -GraphLab PowerGraph is released under the [Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0.html). - -If you use GraphLab PowerGraph in your research, please cite our paper: -``` - @inproceedings{Low+al:uai10graphlab, - title = {GraphLab: A New Parallel Framework for Machine Learning}, - author = {Yucheng Low and - Joseph Gonzalez and - Aapo Kyrola and - Danny Bickson and - Carlos Guestrin and - Joseph M. Hellerstein}, - booktitle = {Conference on Uncertainty in Artificial Intelligence (UAI)}, - month = {July}, - year = {2010} +# PowerLyra v1.0 +## License + +PowerLyra is released under the [Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0.html). + +If you use PowerLyra in your research, please cite our paper: +``` + @inproceedings{Chen:eurosys2015powerlyra, + title = {PowerLyra: Differentiated Graph Computation and Partitioning on Skewed Graphs}, + author = {Chen, Rong and Shi, Jiaxin and Chen, Yanzhe and Chen, Haibo}, + booktitle = {Proceedings of the Tenth European Conference on Computer Systems}, + series = {EuroSys '15}, + year = {2015}, + location = {Bordeaux, France}, } ``` -# Academic and Conference Papers - -Joseph E. Gonzalez, Yucheng Low, Haijie Gu, Danny Bickson, and Carlos Guestrin (2012). "[PowerGraph: Distributed Graph-Parallel Computation on Natural Graphs](https://www.usenix.org/conference/osdi12/technical-sessions/presentation/gonzalez)." Proceedings of the 10th USENIX Symposium on Operating Systems Design and Implementation (OSDI '12). - -Yucheng Low, Joseph Gonzalez, Aapo Kyrola, Danny Bickson, Carlos Guestrin and Joseph M. Hellerstein (2012). "[Distributed GraphLab: A Framework for Machine Learning and Data Mining in the Cloud](http://vldb.org/pvldb/vol5/p716_yuchenglow_vldb2012.pdf)." Proceedings of the VLDB Endowment (PVLDB). - -Yucheng Low, Joseph Gonzalez, Aapo Kyrola, Danny Bickson, Carlos Guestrin, and Joseph M. Hellerstein (2010). "[GraphLab: A New Parallel Framework for Machine Learning](http://arxiv.org/pdf/1006.4990v1.pdf)." Conference on Uncertainty in Artificial Intelligence (UAI). -Li, Kevin; Gibson, Charles; Ho, David; Zhou, Qi; Kim, Jason; Buhisi, Omar; Brown, Donald E.; Gerber, Matthew, "[Assessment of machine learning algorithms in cloud computing frameworks](http://ieeexplore.ieee.org/xpl/articleDetails.jsp?reload=true&arnumber=6549501)", Systems and Information Engineering Design Symposium (SIEDS), 2013 IEEE, pp.98,103, 26-26 April 2013 +## Introduction -[Towards Benchmarking Graph-Processing Platforms](http://sc13.supercomputing.org/sites/default/files/PostersArchive/post152.html). by Yong Guo (Delft University of Technology), Marcin Biczak (Delft University of Technology), Ana Lucia Varbanescu (University of Amsterdam), Alexandru Iosup (Delft University of Technology), Claudio Martella (VU University Amsterdam), Theodore L. Willke (Intel Corporation), in Super Computing 13 +PowerLyra is based on the latest codebase of GraphLab PowerGraph (a distributed graph computation framework written in C++) and can seamlessly support all GraphLab toolkits. PowerLyra provides several new hybrid execution engines and partitioning algorithms to achieve optimal performance by leveraging input graph properties (e.g., power-law and bipartite). -Aapo Kyrola, Guy Blelloch, and Carlos Guestrin (2012). "[GraphChi: Large-Scale Graph computation on Just a PC](https://www.usenix.org/conference/osdi12/technical-sessions/presentation/kyrola)." Proceedings of the 10th USENIX Symposium on Operating Systems Design and Implementation (OSDI '12). +PowerLyra New Features: +* **Hybrid computation engine:** Exploit the locality of low-degree vertices and the parallelism of high-degree vertices -# The Software Stack +* **Hybrid partitioning algorithm:** Differentiate the partitioning algorithms for different types of vertices -The GraphLab PowerGraph project consists of a core API and a collection of high-performance machine learning and data mining toolkits built on top. The API is written in C++ and built on top of standard cluster and cloud technologies. Inter-process communication is accomplished over TCP-IP and MPI is used to launch and manage GraphLab PowerGraph programs. Each process is multithreaded to fully utilize the multicore resources available on modern cluster nodes. It supports reading and writing to both Posix and HDFS filesystems. +* **Diverse scheduling strategy:** Provide both synchronous and asynchronous computation engines -![GraphLab PowerGraph Software Stack](images/gl_os_software_stack.png "GraphLab Software Stack") +* **Compatible API:** Seamlessly support all GraphLab toolkits -GraphLab PowerGraph has a large selection of machine learning methods already implemented (see /toolkits directory in this repo). You can also implement your own algorithms on top of the graph programming API (a certain degree of C++ knowledge is required). +For more details on the PowerLyra see http://ipads.se.sjtu.edu.cn/projects/powerlyra.html, including new features, instructions, etc. -GraphLab PowerGraph Feature Highlights --------------------------------------- -* **Unified multicore/distributed API:** write once run anywhere +## Academic and Conference Papers -* **Tuned for performance:** optimized C++ execution engine leverages extensive multi-threading and asynchronous IO +Rong Chen, Jiaxin Shi, Yanzhe Chen and Haibo Chen. "[PowerLyra: Differentiated Graph Computation and Partitioning on Skewed Graphs](http://ipads.se.sjtu.edu.cn/projects/powerlyra/powerlyra-eurosys-final.pdf)." Proceeding of the 10th ACM SIGOPS European Conference on Computer Systems (EuroSys). Bordeaux, France. April, 2015. -* **Scalable:** Run on large cluster deployments by intelligently placing data and computation +Rong Chen, Jiaxin Shi, Binyu Zang and Haibing Guan. "[BiGraph: Bipartite-oriented Distributed Graph Partitioning for Big Learning](http://ipads.se.sjtu.edu.cn/projects/powerlyra/bigraph-apsys14.pdf)." Proceeding of the 5th Asia-Pacific Workshop on Systems (APSys). Beijing, China. June, 2014. -* **HDFS Integration:** Access your data directly from HDFS +Rong Chen, Jiaxin Shi, Haibo Chen and Binyu Zang. "[Bipartite-oriented Distributed Graph Partitioning for Big Learning](http://ipads.se.sjtu.edu.cn/projects/powerlyra/bigraph-jcst.pdf)." Journal of Computer Science and Technology (JCST), 30(1), pp. 20-29. January, 2015. -* **Powerful Machine Learning Toolkits:** Tackle challenging machine learning problems with ease ## Building -The current version of GraphLab PowerGraph was tested on Ubuntu Linux 64-bit 10.04, 11.04 (Natty), 12.04 (Pangolin) as well as Mac OS X 10.7 (Lion) and Mac OS X 10.8 (Mountain Lion). It requires a 64-bit operating system. - -# Dependencies - -To simplify installation, GraphLab PowerGraph currently downloads and builds most of its required dependencies using CMake’s External Project feature. This also means the first build could take a long time. - -There are however, a few dependencies which must be manually satisfied. - -* On OS X: g++ (>= 4.2) or clang (>= 3.0) [Required] - + Required for compiling GraphLab. - -* On Linux: g++ (>= 4.3) or clang (>= 3.0) [Required] - + Required for compiling GraphLab. - -* *nix build tools: patch, make [Required] - + Should come with most Mac/Linux systems by default. Recent Ubuntu version will require to install the build-essential package. - -* zlib [Required] - + Comes with most Mac/Linux systems by default. Recent Ubuntu version will require the zlib1g-dev package. - -* Open MPI or MPICH2 [Strongly Recommended] - + Required for running GraphLab distributed. - -* JDK 6 or greater [Optional] - + Required for HDFS support - -## Satisfying Dependencies on Mac OS X - -Installing XCode with the command line tools (in XCode 4.3 you have to do this manually in the XCode Preferences -> Download pane), satisfies all of these dependencies. - -## Satisfying Dependencies on Ubuntu - -All the dependencies can be satisfied from the repository: - - sudo apt-get update - sudo apt-get install gcc g++ build-essential libopenmpi-dev openmpi-bin default-jdk cmake zlib1g-dev git - -# Downloading GraphLab PowerGraph - -You can download GraphLab PowerGraph directly from the Github Repository. Github also offers a zip download of the repository if you do not have git. - -The git command line for cloning the repository is: - - git clone https://github.com/graphlab-code/graphlab.git - cd graphlab - - -# Compiling and Running - -``` -./configure -``` - -In the graphlabapi directory, will create two sub-directories, release/ and debug/ . cd into either of these directories and running make will build the release or the debug versions respectively. Note that this will compile all of GraphLab, including all toolkits. Since some toolkits require additional dependencies (for instance, the Computer Vision toolkit needs OpenCV), this will also download and build all optional dependencies. - -We recommend using make’s parallel build feature to accelerate the compilation process. For instance: - -``` -make -j4 -``` - -will perform up to 4 build tasks in parallel. When building in release/ mode, GraphLab does require a large amount of memory to compile with the heaviest toolkit requiring 1GB of RAM. - -Alternatively, if you know exactly which toolkit you want to build, cd into the toolkit’s sub-directory and running make, will be significantly faster as it will only download the minimal set of dependencies for that toolkit. For instance: - -``` -cd release/toolkits/graph_analytics -make -j4 -``` - -will build only the Graph Analytics toolkit and will not need to obtain OpenCV, Eigen, etc used by the other toolkits. - -## Compilation Issues -If you encounter issues please post the following on the [GraphLab forum](http://forum.graphlab.com). - -* detailed description of the problem you are facing -* OS and OS version -* output of uname -a -* hardware of the machine -* utput of g++ -v and clang++ -v -* contents of graphlab/config.log and graphlab/configure.deps - -# Writing Your Own Apps - -There are two ways to write your own apps. - -* To work in the GraphLab PowerGraph source tree, (recommended) -* Install and link against Graphlab PowerGraph (not recommended) - - -## 1: Working in the GraphLab PowerGraph Source Tree - -This is the best option if you just want to try using GraphLab PowerGraph quickly. GraphLab PowerGraph -uses the CMake build system which enables you to quickly create -a C++ project without having to write complicated Makefiles. - -1. Create your own sub-directory in the apps/ directory. for example apps/my_app - -2. Create a CMakeLists.txt in apps/my_app containing the following lines: - - project(GraphLab) - add_graphlab_executable(my_app [List of cpp files space separated]) - -3. Substituting the right values into the square brackets. For instance: - - project(GraphLab) - add_graphlab_executable(my_app my_app.cpp) - -4. Running "make" in the apps/ directory of any of the build directories -should compile your app. If your app does not show up, try running - - cd [the GraphLab API directory] - touch apps/CMakeLists.txt - - -## 2: Installing and Linking Against GraphLab PowerGraph - -To install and use GraphLab PowerGraph this way will require your system -to completely satisfy all remaining dependencies, which GraphLab PowerGraph normally -builds automatically. This path is not extensively tested and is -**not recommended** - -You will require the following additional dependencies - - libevent (>=2.0.18) - - libjson (>=7.6.0) - - libboost (>=1.53) - - libhdfs (required for HDFS support) - - tcmalloc (optional) - -Follow the instructions in the [Compiling] section to build the release/ -version of the library. Then cd into the release/ build directory and -run make install . This will install the following: - -* include/graphlab.hpp - + The primary GraphLab header -* include/graphlab/... - + The folder containing the headers for the rest of the GraphLab library -* lib/libgraphlab.a - + The GraphLab static library. - -Once you have installed GraphLab PowerGraph you can compile your program by running: - -``` -g++ -O3 -pthread -lzookeeper_mt -lzookeeper_st -lboost_context -lz -ltcmalloc -levent -levent_pthreads -ljson -lboost_filesystem -lboost_program_options -lboost_system -lboost_iostreams -lboost_date_time -lhdfs -lgraphlab hello_world.cpp -``` - -If you have compiled with MPI support, you will also need - - -lmpi -lmpi++ - -# Tutorials -See [tutorials](TUTORIALS.md) - -# Datasets -The following are data sets links we found useful when getting started with GraphLab PowerGraph. - -##Social Graphs -* [Stanford Large Network Dataset (SNAP)](http://snap.stanford.edu/data/index.html) -* [Laboratory for Web Algorithms](http://law.di.unimi.it/datasets.php) - -##Collaborative Filtering -* [Million Song dataset](http://labrosa.ee.columbia.edu/millionsong/) -* [Movielens dataset GroupLens](http://grouplens.org/datasets/movielens/) -* [KDD Cup 2012 by Tencent, Inc.](https://www.kddcup2012.org/) -* [University of Florida sparse matrix collection](http://www.cise.ufl.edu/research/sparse/matrices/) - -##Classification -* [Airline on time performance](http://stat-computing.org/dataexpo/2009/) -* [SF restaurants](http://missionlocal.org/san-francisco-restaurant-health-inspections/) - -##Misc -* [Amazon Web Services public datasets](http://aws.amazon.com/datasets) - -# Release Notes -##### **map_reduce_vertices/edges and transform_vertices/edges are not parallelized on Mac OS X** - -These operations currently rely on OpenMP for parallelism. - -On OS X 10.6 and earlier, gcc 4.2 has several OpenMP bugs and is not stable enough to use reliably. - -On OS X 10.7, the clang -++ compiler does not yet support OpenMP. - -##### **map_reduce_vertices/edges and transform_vertices/edges use a lot more processors than what was specified in –ncpus** +The building, installation and tutorial of PowerLyra fully follow that of GraphLab PowerGraph. See README_GraphLab.txt for details. -This is related to the question above. While there is a simple temporary solution (omp_set_num_threads), we intend to properly resolve the issue by not using openMP at all. -##### **Unable to launch distributed GraphLab when each machine has multiple network interfaces** -The communication initialization currently takes the first non-localhost IP address as the machine’s IP. A more reliable solution will be to use the hostname used by MPI. diff --git a/README_graphlab.md b/README_graphlab.md new file mode 100644 index 0000000000..529ec7b15a --- /dev/null +++ b/README_graphlab.md @@ -0,0 +1,269 @@ +# GraphLab PowerGraph v2.2 + +## UPDATE: For a significant evolution of this codebase, see GraphLab Create which is available for download at [dato.com](http://dato.com) + +## History +In 2013, the team that created GraphLab PowerGraph started the Seattle-based company, GraphLab, Inc. The learnings from GraphLab PowerGraph and GraphChi projects have culminated into GraphLab Create, a enterprise-class data science platform for data scientists and software engineers that can simplify building and deploying advanced machine learning models as a RESTful predictive service. In January 2015, GraphLab, Inc. was renamed to Dato, Inc. See [dato.com](http://dato.com) for more information. + +## Status +GraphLab PowerGraph is no longer in active development by the founding team. GraphLab PowerGraph is now supported by the community at [http://forum.dato.com/](http://forum.dato.com/). + +# Introduction + +GraphLab PowerGraph is a graph-based, high performance, distributed computation framework written in C++. + +The GraphLab PowerGraph academic project was started in 2009 at Carnegie Mellon University to develop a new parallel computation abstraction tailored to machine learning. GraphLab PowerGraph 1.0 employed shared-memory design. In GraphLab PowerGraph 2.1, the framework was redesigned to target the distributed environment. It addressed the difficulties with real-world power-law graphs and achieved unparalleled performance at the time. In GraphLab PowerGraph 2.2, the Warp System was introduced and provided a new flexible, distributed architecture around fine-grained user-mode threading (fibers). The Warp System allows one to easily extend the abstraction, to improve optimization for example, while also improving usability. + +GraphLab PowerGraph is the culmination of 4-years of research and development into graph computation, distributed computing, and machine learning. GraphLab PowerGraph scales to graphs with billions of vertices and edges easily, performing orders of magnitude faster than competing systems. GraphLab PowerGraph combines advances in machine learning algorithms, asynchronous distributed graph computation, prioritized scheduling, and graph placement with optimized low-level system design and efficient data-structures to achieve unmatched performance and scalability in challenging machine learning tasks. + +Related is GraphChi, a spin-off project separate from the GraphLab PowerGraph project. GraphChi was designed to run very large graph computations on just a single machine, by using a novel algorithm for processing the graph from disk (SSD or hard drive) enabling a single desktop computer (actually a Mac Mini) to tackle problems that previously demanded an entire cluster. For more information, see [https://github.com/GraphChi](https://github.com/GraphChi). + +# License + + +GraphLab PowerGraph is released under the [Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0.html). + +If you use GraphLab PowerGraph in your research, please cite our paper: +``` + @inproceedings{Low+al:uai10graphlab, + title = {GraphLab: A New Parallel Framework for Machine Learning}, + author = {Yucheng Low and + Joseph Gonzalez and + Aapo Kyrola and + Danny Bickson and + Carlos Guestrin and + Joseph M. Hellerstein}, + booktitle = {Conference on Uncertainty in Artificial Intelligence (UAI)}, + month = {July}, + year = {2010} + } +``` + +# Academic and Conference Papers + +Joseph E. Gonzalez, Yucheng Low, Haijie Gu, Danny Bickson, and Carlos Guestrin (2012). "[PowerGraph: Distributed Graph-Parallel Computation on Natural Graphs](https://www.usenix.org/conference/osdi12/technical-sessions/presentation/gonzalez)." Proceedings of the 10th USENIX Symposium on Operating Systems Design and Implementation (OSDI '12). + +Yucheng Low, Joseph Gonzalez, Aapo Kyrola, Danny Bickson, Carlos Guestrin and Joseph M. Hellerstein (2012). "[Distributed GraphLab: A Framework for Machine Learning and Data Mining in the Cloud](http://vldb.org/pvldb/vol5/p716_yuchenglow_vldb2012.pdf)." Proceedings of the VLDB Endowment (PVLDB). + +Yucheng Low, Joseph Gonzalez, Aapo Kyrola, Danny Bickson, Carlos Guestrin, and Joseph M. Hellerstein (2010). "[GraphLab: A New Parallel Framework for Machine Learning](http://arxiv.org/pdf/1006.4990v1.pdf)." Conference on Uncertainty in Artificial Intelligence (UAI). + +Li, Kevin; Gibson, Charles; Ho, David; Zhou, Qi; Kim, Jason; Buhisi, Omar; Brown, Donald E.; Gerber, Matthew, "[Assessment of machine learning algorithms in cloud computing frameworks](http://ieeexplore.ieee.org/xpl/articleDetails.jsp?reload=true&arnumber=6549501)", Systems and Information Engineering Design Symposium (SIEDS), 2013 IEEE, pp.98,103, 26-26 April 2013 + +[Towards Benchmarking Graph-Processing Platforms](http://sc13.supercomputing.org/sites/default/files/PostersArchive/post152.html). by Yong Guo (Delft University of Technology), Marcin Biczak (Delft University of Technology), Ana Lucia Varbanescu (University of Amsterdam), Alexandru Iosup (Delft University of Technology), Claudio Martella (VU University Amsterdam), Theodore L. Willke (Intel Corporation), in Super Computing 13 + +Aapo Kyrola, Guy Blelloch, and Carlos Guestrin (2012). "[GraphChi: Large-Scale Graph computation on Just a PC](https://www.usenix.org/conference/osdi12/technical-sessions/presentation/kyrola)." Proceedings of the 10th USENIX Symposium on Operating Systems Design and Implementation (OSDI '12). + + +# The Software Stack + +The GraphLab PowerGraph project consists of a core API and a collection of high-performance machine learning and data mining toolkits built on top. The API is written in C++ and built on top of standard cluster and cloud technologies. Inter-process communication is accomplished over TCP-IP and MPI is used to launch and manage GraphLab PowerGraph programs. Each process is multithreaded to fully utilize the multicore resources available on modern cluster nodes. It supports reading and writing to both Posix and HDFS filesystems. + +![GraphLab PowerGraph Software Stack](images/gl_os_software_stack.png "GraphLab Software Stack") + +GraphLab PowerGraph has a large selection of machine learning methods already implemented (see /toolkits directory in this repo). You can also implement your own algorithms on top of the graph programming API (a certain degree of C++ knowledge is required). + +GraphLab PowerGraph Feature Highlights +-------------------------------------- + +* **Unified multicore/distributed API:** write once run anywhere + +* **Tuned for performance:** optimized C++ execution engine leverages extensive multi-threading and asynchronous IO + +* **Scalable:** Run on large cluster deployments by intelligently placing data and computation + +* **HDFS Integration:** Access your data directly from HDFS + +* **Powerful Machine Learning Toolkits:** Tackle challenging machine learning problems with ease + +## Building + +The current version of GraphLab PowerGraph was tested on Ubuntu Linux 64-bit 10.04, 11.04 (Natty), 12.04 (Pangolin) as well as Mac OS X 10.7 (Lion) and Mac OS X 10.8 (Mountain Lion). It requires a 64-bit operating system. + +# Dependencies + +To simplify installation, GraphLab PowerGraph currently downloads and builds most of its required dependencies using CMake’s External Project feature. This also means the first build could take a long time. + +There are however, a few dependencies which must be manually satisfied. + +* On OS X: g++ (>= 4.2) or clang (>= 3.0) [Required] + + Required for compiling GraphLab. + +* On Linux: g++ (>= 4.3) or clang (>= 3.0) [Required] + + Required for compiling GraphLab. + +* *nix build tools: patch, make [Required] + + Should come with most Mac/Linux systems by default. Recent Ubuntu version will require to install the build-essential package. + +* zlib [Required] + + Comes with most Mac/Linux systems by default. Recent Ubuntu version will require the zlib1g-dev package. + +* Open MPI or MPICH2 [Strongly Recommended] + + Required for running GraphLab distributed. + +* JDK 6 or greater [Optional] + + Required for HDFS support + +## Satisfying Dependencies on Mac OS X + +Installing XCode with the command line tools (in XCode 4.3 you have to do this manually in the XCode Preferences -> Download pane), satisfies all of these dependencies. + +## Satisfying Dependencies on Ubuntu + +All the dependencies can be satisfied from the repository: + + sudo apt-get update + sudo apt-get install gcc g++ build-essential libopenmpi-dev openmpi-bin default-jdk cmake zlib1g-dev git + +# Downloading GraphLab PowerGraph + +You can download GraphLab PowerGraph directly from the Github Repository. Github also offers a zip download of the repository if you do not have git. + +The git command line for cloning the repository is: + + git clone https://github.com/graphlab-code/graphlab.git + cd graphlab + + +# Compiling and Running + +``` +./configure +``` + +In the graphlabapi directory, will create two sub-directories, release/ and debug/ . cd into either of these directories and running make will build the release or the debug versions respectively. Note that this will compile all of GraphLab, including all toolkits. Since some toolkits require additional dependencies (for instance, the Computer Vision toolkit needs OpenCV), this will also download and build all optional dependencies. + +We recommend using make’s parallel build feature to accelerate the compilation process. For instance: + +``` +make -j4 +``` + +will perform up to 4 build tasks in parallel. When building in release/ mode, GraphLab does require a large amount of memory to compile with the heaviest toolkit requiring 1GB of RAM. + +Alternatively, if you know exactly which toolkit you want to build, cd into the toolkit’s sub-directory and running make, will be significantly faster as it will only download the minimal set of dependencies for that toolkit. For instance: + +``` +cd release/toolkits/graph_analytics +make -j4 +``` + +will build only the Graph Analytics toolkit and will not need to obtain OpenCV, Eigen, etc used by the other toolkits. + +## Compilation Issues +If you encounter issues please post the following on the [GraphLab forum](http://forum.graphlab.com). + +* detailed description of the problem you are facing +* OS and OS version +* output of uname -a +* hardware of the machine +* utput of g++ -v and clang++ -v +* contents of graphlab/config.log and graphlab/configure.deps + +# Writing Your Own Apps + +There are two ways to write your own apps. + +* To work in the GraphLab PowerGraph source tree, (recommended) +* Install and link against Graphlab PowerGraph (not recommended) + + +## 1: Working in the GraphLab PowerGraph Source Tree + +This is the best option if you just want to try using GraphLab PowerGraph quickly. GraphLab PowerGraph +uses the CMake build system which enables you to quickly create +a C++ project without having to write complicated Makefiles. + +1. Create your own sub-directory in the apps/ directory. for example apps/my_app + +2. Create a CMakeLists.txt in apps/my_app containing the following lines: + + project(GraphLab) + add_graphlab_executable(my_app [List of cpp files space separated]) + +3. Substituting the right values into the square brackets. For instance: + + project(GraphLab) + add_graphlab_executable(my_app my_app.cpp) + +4. Running "make" in the apps/ directory of any of the build directories +should compile your app. If your app does not show up, try running + + cd [the GraphLab API directory] + touch apps/CMakeLists.txt + + +## 2: Installing and Linking Against GraphLab PowerGraph + +To install and use GraphLab PowerGraph this way will require your system +to completely satisfy all remaining dependencies, which GraphLab PowerGraph normally +builds automatically. This path is not extensively tested and is +**not recommended** + +You will require the following additional dependencies + - libevent (>=2.0.18) + - libjson (>=7.6.0) + - libboost (>=1.53) + - libhdfs (required for HDFS support) + - tcmalloc (optional) + +Follow the instructions in the [Compiling] section to build the release/ +version of the library. Then cd into the release/ build directory and +run make install . This will install the following: + +* include/graphlab.hpp + + The primary GraphLab header +* include/graphlab/... + + The folder containing the headers for the rest of the GraphLab library +* lib/libgraphlab.a + + The GraphLab static library. + +Once you have installed GraphLab PowerGraph you can compile your program by running: + +``` +g++ -O3 -pthread -lzookeeper_mt -lzookeeper_st -lboost_context -lz -ltcmalloc -levent -levent_pthreads -ljson -lboost_filesystem -lboost_program_options -lboost_system -lboost_iostreams -lboost_date_time -lhdfs -lgraphlab hello_world.cpp +``` + +If you have compiled with MPI support, you will also need + + -lmpi -lmpi++ + +# Tutorials +See [tutorials](TUTORIALS.md) + +# Datasets +The following are data sets links we found useful when getting started with GraphLab PowerGraph. + +##Social Graphs +* [Stanford Large Network Dataset (SNAP)](http://snap.stanford.edu/data/index.html) +* [Laboratory for Web Algorithms](http://law.di.unimi.it/datasets.php) + +##Collaborative Filtering +* [Million Song dataset](http://labrosa.ee.columbia.edu/millionsong/) +* [Movielens dataset GroupLens](http://grouplens.org/datasets/movielens/) +* [KDD Cup 2012 by Tencent, Inc.](https://www.kddcup2012.org/) +* [University of Florida sparse matrix collection](http://www.cise.ufl.edu/research/sparse/matrices/) + +##Classification +* [Airline on time performance](http://stat-computing.org/dataexpo/2009/) +* [SF restaurants](http://missionlocal.org/san-francisco-restaurant-health-inspections/) + +##Misc +* [Amazon Web Services public datasets](http://aws.amazon.com/datasets) + +# Release Notes +##### **map_reduce_vertices/edges and transform_vertices/edges are not parallelized on Mac OS X** + +These operations currently rely on OpenMP for parallelism. + +On OS X 10.6 and earlier, gcc 4.2 has several OpenMP bugs and is not stable enough to use reliably. + +On OS X 10.7, the clang +++ compiler does not yet support OpenMP. + +##### **map_reduce_vertices/edges and transform_vertices/edges use a lot more processors than what was specified in –ncpus** + +This is related to the question above. While there is a simple temporary solution (omp_set_num_threads), we intend to properly resolve the issue by not using openMP at all. + +##### **Unable to launch distributed GraphLab when each machine has multiple network interfaces** + +The communication initialization currently takes the first non-localhost IP address as the machine’s IP. A more reliable solution will be to use the hostname used by MPI. \ No newline at end of file diff --git a/license/LICENSE_prepend.txt b/license/LICENSE_prepend.txt index 55c9bf7589..be781ca9b8 100644 --- a/license/LICENSE_prepend.txt +++ b/license/LICENSE_prepend.txt @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2013 Shanghai Jiao Tong University. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * For more about this software visit: + * + * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html + * + */ + /* * Copyright (c) 2009 Carnegie Mellon University. * All rights reserved. From 6d0a93011511482380b23286a80d873b8ae60a07 Mon Sep 17 00:00:00 2001 From: Chen Rong Date: Tue, 9 Jun 2015 16:07:58 +0800 Subject: [PATCH 39/50] update readme --- README.md | 14 ++++++++++++++ images/hybrid_cut.png | Bin 0 -> 106797 bytes images/hybrid_engine.png | Bin 0 -> 36999 bytes 3 files changed, 14 insertions(+) create mode 100755 images/hybrid_cut.png create mode 100755 images/hybrid_engine.png diff --git a/README.md b/README.md index bb95bc3f0f..f2f4ceee90 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,20 @@ PowerLyra New Features: For more details on the PowerLyra see http://ipads.se.sjtu.edu.cn/projects/powerlyra.html, including new features, instructions, etc. +### Hybrid Computation Engine + +We argue that skewed distribution in natural graphs also calls for differentiated processing of high-degree and low-degree vertices. PowerLyra uses Pregel/GraphLab-like computation models for process low-degree vertices to minimize computation, communication and synchronization overhead, and uses PowerGraph-like computation model for process high-degree vertices to reduce load imbalance, contention and memory pressure. PowerLyra follows the interface of GAS (Gather, Apply and Scatter) model and can seamlessly support various graph algorithms (e.g., all GraphLab toolkits). + +![Hybrid Computation Engine](images/hybrid_engine_.png "Hybrid Computation Engine") + + +### Hybrid Graph Partitioning + +PowerLyra additionally proposes a new hybrid graph cut algorithm that embraces the best of both worlds in edge-cut and vertex-cut, which evenly distributes low-degree vertices along with their edges like edge-cut, and evenly distributes edges of high-degree vertices like vertex-cut. Both theoretical analysis and empirical validation show that the expected replication factor of random hybrid-cut is alway better than both random (Hash-based), contrained (e.g., Grid), and heuristic (e.g., Oblivious or Coordinated) vertex-cut for skewed power-law graphs. + +![Hybrid Partitioning Algorithms](images/hybrid_cut_.png "Hybrid Graph Partitioning") + + ## Academic and Conference Papers Rong Chen, Jiaxin Shi, Yanzhe Chen and Haibo Chen. "[PowerLyra: Differentiated Graph Computation and Partitioning on Skewed Graphs](http://ipads.se.sjtu.edu.cn/projects/powerlyra/powerlyra-eurosys-final.pdf)." Proceeding of the 10th ACM SIGOPS European Conference on Computer Systems (EuroSys). Bordeaux, France. April, 2015. diff --git a/images/hybrid_cut.png b/images/hybrid_cut.png new file mode 100755 index 0000000000000000000000000000000000000000..d220604a87bc17cb1bef4b32c427483efa0ab7ec GIT binary patch literal 106797 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

m#8E-tO% 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

kRw$x+x||J`7yek?bH#)U|4%FQxqHIKllz*8w( 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 S|N0jACnuvUT_pM1@Bag1D||5k literal 0 HcmV?d00001 diff --git a/images/hybrid_engine.png b/images/hybrid_engine.png new file mode 100755 index 0000000000000000000000000000000000000000..45cffb1ee68d0ef148f2f462740bc2962eae9335 GIT binary patch literal 36999 zcmd?QWmweF*DegA3@9>Eg2aGGD%~-3cS<+XE#**B(%l_W(jcWY(%m7QL+8->&fx!f z&hx(K+xzhxytthC#ol|ZeXq6dwbmwBQC{NZbHe9HNJuZGBt?~xke&jOkdV31P~lhD z0J9eG|BxM(C4`>{wt9BLzo6SoYC0kz;bS9ykyjlT8%GBxEynwZ)YmUe^H!dA_neD#>aS`D^?Bw+C(u$K4Wy>G2Zc0%*~qI-<26lJ9GJMVmg_SO{*lzbJt6*j7W zuP>kBk@58&mbR(!^|t$~H8Na)e?Px`#Q*Cfk~g*a)BpbR#rOZ`4M8p}Lro5!TEs{J zWQRFeh!3x%MO(p-SE&CFz7o@3dmjGE`~_Ul-?&&za8x0Auk_xuUn#bj8M5rU2>}_9|PW#~Ff**0jES>3!(8=nt!)CLpQ3LrDU7 zf8Kncv9Zc}P!dkfJ>#iir?N`ZrW&C_x`gH^Ng2yx%-tJ;C~nC3sz{&69 z?(}XCLriZv>8_NAxJQ!Tx!?VY+NWZ5);q>)Cwl}0U5cvX|DeS znozMHg0EZ9=o1qn_0)KfLCLFdn3CqK<)M3gVwTb2M6p>8W$71?B^J;Dc(uvPQXq$7lDoOg#iG7+GX3=G z+={e55U{xo3t+j5TLEhcE@evL@Z^Knqp3Hhd zK9iR~1R3#EJ+uA-Hg*cpHkBwzwwofC^y3G;5ALT*e^Z869S}&|x@&75H1f1M%6pJX z%Q~Ozhn(6OgCM`Q&;R(51`vS=Ke4Sdr))){3V7fPANYZw_%ExwMdJcpZRcvZP6-jP z@ni?mM2C2T5Ao92WCP(}dK?4~o?l)gCGXjDOP>l2rQ)c+l_ZLtE2p9m*qTjgZj;=( z?!7B9CXP}XarC6R@oc`E4Ge||KJ4P0lH}u3yH752(qFC2Ezk5sX|RAn+*k1^wQV}B zo^(f*Zv+|v=I@g6u1OG68~hsZ%esn`^Dk=X+M#nL`BZ4t*Iuo==+bsO!wWnkk;~;= zeP=mT=xtLq|0dhO6y!8Cca!(+-b9w^Zmu*vyn5E-6)ek`__R4_rrI`{q=WmSSodxK z#S4kEx5e>oP0yFswN&7b7lc9*+9yGSWH%b7gGRSUAm#GOrvC`f3giJAri*SkhYtP5pwQ&eZ6ezZ!Du61lIr}a z*m$7p;P2`$ab3M{W57BNvaD`72UB_otM~Ru2Hdtb&)*!)O}%-i%<#kFvyco(Zp$m| zMjRtj%u6zp9|bRMUf(WkV#RbN|a z)DPS5RTRj%8GGEi`HWsUJtnDOALS2Jfa&g$_Pf~ZKxejXaiTLcX zfv$;;$!6gyaZXXJc(vJK!Zhu0vt?3*33_=$0sYcZj#24a0xZ>Z@U~r9kryuW^%tIkVyFe?r(d2kZ*!7}#FClC=b9OT=xA57_Np>}#&oC~ zB3!G8AxPFW71^ua7c{k@lSN9o97b*rQXOP?1D1*_Gmc2H%ZE!$28#k zG}Y|@`c?Lv@r~G3cG86s6T9H$%{x?2Yvr#XH<%!IAX4{uq@%nkDJoP*#q9`mad}^D zw5V{&w|;W?+9~HX#)U{X+cxYj=KBjm)U!91LQZi`I`rXP$xLiCYIk~z710$X=7moN z{NBPb`r}^OULLhm66&)Tm-`(7veMv`% zP9#T>;1*(@hK|xyYs&Id^?JV~QO^&;L;)32{hkC^2oA-zJ5qYJ0~W!7k@cL!NDG5P zcL|R~%(hHkyT1T#*S#G~#Q-NpZ#I-R{O-B?<@MQ!o%Tum`qjB3De{0HW&6@V{iMLr z)j~{M&vVdHv4lovR&Jvq&6^;<(O3Jm=H{RYKMa#o?01%_h?s+PVhnNu?qb%GaO3}K zI!i3Tjk1M};>yVD#pub>-Bh=K!M+w}m-tg%dGe}gA>ib50paa6_xRKKPzKk{8_#!b zH?!wGxL;vpGd+Flh_LsR38;qgm33R~p>@;sB&hdi_eH|H5+g;$p+ZXo7FmDuI!zIn zLGPi4$9dKJWEx6DyuG#o^#Gx)D?@pFq;$(YD6pc7#&v^S;|bz^$*4~&E{i9`tcfO= zRzjdg-Lkb@?URG0@e8bTo|*+<7y&B;iX) z#Xu$2zPqB!o%lQICfP|Qb#NwAg7sT#t#Q4_2V-9Bf?)=WvXqV~go;N1Jc)-Of{LmrLbtoQ8rA-(^9V3@?e! z2HiW4Tw_rgao0J|vJad$vfbLmG6@t`PMK(@8fPO4Ld7SQ**Tj~VFu;{er=QBpyEH} zd-tiy0H0~O+BG_=T&^ACmAZG4{39uVaivX`K61g*%elzig2rUpOPvDI;X3^rG;vtH4?ar$ z8FkBjVN8lV==XEQuJ3cHH_}ZC6BDqjy=)-M+H6qHkCv)S)!%dAItj3*RD+v}J7hj8 z_PC{%AZwt_^^uznaLo%9>G#FkS(#LSUQBvQ7lHasq4y$xo@14*RjU)-lok>j#q}z; zlxOM$b0sBXKOk5O9LZ-$Ezu~~M&3)w6jN7O?+A5j*D$_mkKfwR=6lNCBmumv-UCd~ z5lk{l044~jDDmfe{ny`hTANw|t>iSL|9k?KJF{n;zzN!1y_A0wgh~i`qq9b1Do**w znb5UCUMTSwho;fUhox?_e7FsxdghUCrd{$sNdkl;wb0(+(Q%A$;h+N7vu~<$8MRVc z20N=G9Y|pY45r~Kfp9XNH4OD()n9Bx_0cyt=LGty|8`{s#;v;QS=}+v*wkT9^(aPI z4HSoOgbXqBZIzwfT=$+K-`;%siSS--l%R`&@G>cH80M0lQzNi)AWZO~C-RH} zbC{W)z~Jn^rwa0lJ+graML8la!Dc>#zYv$ctYNeGzzuzYaKy83WqAMWv}QvfC+uq?dTt~c;B z29~u1ZIdP*Y68E^%7h9LcN5W9_K93OjlbrB?OnbUd3)eB5xKiLJ$wy;M_vgem;r68 zT?`u#YAjt6YHLSilvc}hUR zm+=q$f33LuKLp`d*pSO=M~9sp0O=3A}b3>OF+(=+*i&sTgRR zj_kGZl$?GCmyIDsYxTF!Po^>9SoZrgh+>l5Ji-~O6Ft2IM$^ONwuK2gz^sm~CA66w z-bWY-cmccUUBZ39rWBF$E-h zfchwFFgRsvY|Gg#xPR6CfFtMZRZOZ$wo#lBjqX6=>V2LryUIiRj-v~^?_%7+yyl<~ zX=TvwKF#suW(yvnh?ZTcplx4d#j+Fmo>R&DkD3b&ON?3>qwDY@EIx)E7-td!ZBLg` zr!>-sk%b|@YJpUE4k)EPkjb!%YfCFJmR&pC!QFLmhF#0~C;(a<&1iw2|J@_W#u{J9n&{xf5`7$WvsDyaVZ-IG zEj+r{SX)3O`d7gA?_1 zbQOD^9*(#*pyx1+bE*?mh&?PowN}Xh=Q}*1g$gYV`VG181P>g#+agPA_A%l>eLNP2 z8h@Ivc29z7oDQikYZl9T=3js4^wMwfh6=52&P95D8UfQt0yhS;L{=^@ioNV^^W2`6 zG65I+e*a1aJTnp4cy;5DuU}$e$@xz#1CYI!R&+jgnE&OSa#USoUud9nY+ekvSxp+? ziYp-z!^^hqBClCvM3*|x+hO^9DT76r(dhKI4tHem@_yE9O-wt`>0w&zl0?D)%Kc(U z?u$PYMOr8BYvmqh;0}Iqyl_@S115iNe6wpYkx9u%o9+aUP_&1vaN|1lLrprtQJdtb zFDuqDTzUVR=J5LRY@Qsk*M$s7=_Al3Fa!@aJu#5hrPSwD&!0ZH41H4d`Kc`YuE&qB zVV@rEa>v#Bk%z@V*j)Ivd1@k_st(IMNi{dOj3Gybh+iZA=R0aOd(6Y%f_|`OC zGycN?AQ?Jsp$_?!XC)U{WEA$);vuCMO%bw=#As!4Tal-K&XfWfDL@dZH*CPZm;eo+ z@{QoAgfnTnkqng>`|=eh++S(}Cs8tF24x=BJK2t?AGn?V+ja5V*Daf0jREWpBj1E{TC)D9sPVe6k1f}L z+XQ{R3MkBH?g0yz7$e@b6hci}@*Cr@!%Q_)_mRQ9MB5+8-IX5>m@QZfr zPjrLtg>h+rG0so2tmNyJ_DLWXT{J2O!sZ3r2QW4w-M5n#=GVH^a(bng#40`!GR&SK z(7U{<)4WKkq8_`$wIayKu;W2D|IbgLBiD^Y6z5oc0_k+hrRe0o&k=I5^-oI<2?@j3 zOagwmb(pH;MQ(;a?_IqQH%?? zTnz)6ESf6ki!(SrmBoP7!v(RgKE=b$^+# z&>kJ7Q!)Td=h@{=>&iC~-<^7El=Xex*N7V5X(L>1MA*YnU1p{%ahY%Mt?dM5DQ{Op zv$#qf?UL6TAJh!wG@B!#E?dZG%yUM$wb6gB+DkSrXa4XLS!C|5?jPXaeSYC?#uf~R z2;QI-qr!VR%0ho$LN{j)>km^Jys+#Gji_f4cKjnD zQ~JJ}Gfz{m{tu(URQzE?MI*6MLl>Kb*ZZCvy%>FXvirY0Giz3f>fZc3ZCcrrn zthra_zO32vD{CT>si$*)o)7y7{Jsh5J$JUS*nILik6;qKHH|16RKa0l2Su?=6CasO zjl+6Tq^IRN3hE@&8kQ*kZISk|94qtD=oi2<_xtPS-EI4r^0;w4==JXIL+y3yM{-q= z<+Lh&<<;{aVyx!%OTGCNmIMqR}==v7o&ybzrl4wJNfH z?>vy3bg<_MiClZ#>wi%(1uzHEv7`CWeqr`~f_T{i&F90OhCk}z5ZeT6L1v3G*S%cXMU3mTAbnWR8O+P4KVxl?`HAOi=y5XB zhYuU&HnTE#3YMQu*qc486I;{NY6V``MX;E-f6Jpt?3TSx8=oqp4Sv|*H&4TE=}^2- z3cu2MUy{D=8P-{v2VWAw6D;n28`s|iZ>4U^oH6Y0;8kFF*|SwAlZ%jACmjt^C`$+l zc6kQB7=)YDkZ|>aolZA}afxuk9;_pvBY=dcrwV4UMKsD9H;kCP?ci8<1F%*F`HOR3w5y)kvNbrI#yz)whSKy=uW#V|nQ}aYV1(=+v_5ulBKYjkCx4}72 z*=mxSOno9R`ug|jtJX6VM5PvKU3aazfMlW}l;^tCwx)%y{QI})c*J1GfX$r%V5>f+2p*TFeuJ!O7W z+#r1K;YxOk-K0f9m0}mSw$dN0Jx?4R9_fa|1t|-X1%c}e4$Bz94GNc{KNg;=sxSG1 z@unH#Ac`|T&nG!cm-M&lf?s82rt^67yF9-pKq#FJ$OhLuT*dcNs`43e1M+4=id?LQ z6L;L*4k--TtAqi}ug(+eN<8J}X3Z}8MUS@33gA>cI}#agc!DrNc<%rX`ByB#8Aae8 zyr*X9L#9L9tV3SItH#_av5L{MB1CAhIiEmW6c*&Uzy7!Pn1cJz;HjOg&XXpJ8Z0cB0-Q~VdwzB11e&w^zjnTbctI<3KHI`Sucy|~kC#E3@7q+rmv z=m-Iz-pgeL={k9~+vWH;>wm}vZ<^3WMJ|({<)s(L#j#0ENaYTtE?03Y?XRzk_3k}y zOMT}4RQ*Mn;bP3Iq4RR6XTlW$!L~f1_ zP4h2uSl$5C%Ul{pfY#5SFH-%Iw{KcT%}*iYJZd)2oIYXRTk+S50UZn(A=?wCUH(A* zy{7}JU4m!SF1bbn`aJ&#Fwd`6Gx*lkLW z+s&lrH>gyo!mh}jKd5}WQ!`*!^urt5yJn1DJ~uO+9s=e!1V)-gMw(drC@``-o524y z)7u}B7QQ@6?VZvkgNbbW3kCpfFCq_y;mU@RwJ!zq!93N0=lf9ZlTBK?fM`iL5SpIh zwx^(t`PG73oB%CizpG&h)bXCyz$F0wk*AX4ek~|nB&N^ zQP=hFf0)2n;{7`3Q@5D1DyoI0(<|>0z4R$Fn@EiTWN*d>Y+_9Tn0hE&XJ{jLW>NX} z$9;ooJk=ddE{f{oKUX_2#)o-kr`L~kYpAUgC=u7%5O(~erEp6E(Ao)f1h;huXCwAA zN*!j_5Q7h{(D?kHkxl708oe?lXS4}&_SW{be0+3sY+)t@&hYKq-w)(s zzT{%YO#vVwomhf!xss??I~ur#OKBKf)oKf^TXHG3W9@d>U?amTxlXjeXm^Paob5B6 z9@gPOZ)k374;pf&^|}ZKQ5#P)!E;N6#P=+Y5Wj!#TEa z6Uh@eU`M=ya8M#}+P}lS4p|};8F1B+_GQDSdBA+JuF(4H&|sP(cA*NFIsR~QL%zxt zXH9T*U;LFT%%hWpZpZ-CaoLomf*(Y`+rC@t-0wdDnDla~C7&r%upn*lIrLlb&`(>V zn!=7SS!0;X=(*vLGky~T9`FmRe!JR%wAN8!4i6>p`ol7`W4N>rI6M(kFHKi2KXc)1r)bL}?B zCGg3kyF#}=hwJ70L_#V;1NECJ^SOzyMt-k#nS7OBsa@?u2I0vsV~4# zJBmB!=C_3XE*kSm`IBHFIX{X4zrUKiV`g6WK{G?ezBs7g>Ej^Y8XVpls0N@{Bm=IVL5}4IH5gK*HB%)#8ub{slgocng^l!M zFF5_q%2cx&2NAs+goU)xJVihjp5$FNd|Xcd^9*A?!HUXmXH_ycKIDe&oA{dZahA(S zM)FmLWE5a;bhKfAd;4%UG&C}F3EuOms+X3(%~56%Bj1m77||wUF0y!401>QtInQ5Q z{Lbts? zr0vxDAHi|Zo8K?zaz1I&-6vlz?)$dj&eQ&qIlAKLT*dD|S$Wsjc&Z=t^`YeQ=BL5J zVH;*J8^>B!gP@U4zK{;IZ6>LK`(v@ErpG2nTfwuhl$UAChrcryC_Cl^g866p=uC}P ziB|2tez+`KuIP>$wTB`bX+9VuY-W^0EUhOB`gVS2e2S2Ux^ypW@7)3qUywBe zxFqukNqi3IhsVZ-XVOy4qR;W#-d_}FItaL@-7IkKef#FjfqAgeEJZchf~F_i2NtB; zHGYiqv-v?dZ^pnp?@|Z6D=W(@g75IH^~%w_$TZe;M8RVmCB>6BduM0weAV|qUzK;Z zD1qML+%m&CKY|zF_zVS?uaOhhq^F_?ymlqeQv%cn)&~c>&p-+StK7LD&8^NDT41_E zE@~;aU3W!_anfLb(C09g4)3&!R&6@Dm1sWp=FF{2X+@Z zLlp`;>fOIGo7(OUuFh4HEYIA*tIKx3?8+Q7=R+0{BG^5OFkc22VFWDNw3r%c9wOWw zb^igxE`@sTsFO533Z~}bj4p$WF(iJax#Y3RVEj31PcZ%(u?NCbepo?WBk-cSsK2OG zt}2Dn9UTKdRnuhNC~Dx-a+z=5>w(quUTf}QOZWVoXK5Id^`&7{!62=R_G{?3DZVKd-MUVAKp&9B}S>!WdDH_93$!c zY_Dii3{-njQ`>NGr>5@t%RPLFxW%+$>`PZP@ z30}sBQ*a*%Z&#uawf0UYo2ph3(j1Dh`1sNc$k+5hp7-}KWBDK|EBxZJ+k};iXi1nY zXha&c6R4(CL01%h>o7^M$G%_f6NgiDI z#@6k!`S`MGT_sLbtK7~a;83aaru;-?0X;L<;yLzqMG7D%r=F7!@I`CjLbJrCb7VN? zQ6?P3*u>S0tD^AmIX0#?Y$=dFncLj)j{cvip{5g8$Rs$mXr;+@wdZyUy_Pg} zS5jx6pVt$j>t)8r%Q1Y}iy2e(j+UPntF@M2?<_41s03_V%ZIjlD~=R@T2p6;Q_Q29F##uXus+?_k+Ux8$<MXlRG|MtfSNtArq>bjHdeEHZAf-7TS;ARtUm0`H!>UH>vw?K57rTUx_n zNbOS%d^uX+_4d+5owRuJK6^pHeX~S4df24RF*pSfWYXJ(APW-vVO-I|gCbYiqceNS z^@@e_#q>!UG*M9Q_g{qV?bP|na|HDn#dP?JPn)s0`pSHXB~lLYfCAPk^<_BO6C! zCDha{ayNi%GVHd;p^=%-@3(WIw7AWm%FA2I3g3vy=su(`Gc%xKAaZCJne`|u%A1<2ZR}@+!r#tSR+lEP`Hk#@VeXYNqLpyfQ~-iN z+w3#G8|knEjnwFCn4g_So5GU#m{)KUV~SOH@rw1ADMd1Wc6S4Gap0zAra! zqy=qpz$q#HAa=My70N^19<(K8)+DrtZRRI`qpkK${;N+hEWp}5qhse$^@xU&zT*%+ zma%m#v{YTs64luXGqW?8JTK~G!Ec()Slb-!7$*4Iv4j45?QlyqTK#AvZ&AYmCr*i? z&2Dlm)panX(prn@UF{S~LVawWZI@#bt=}tNHfA^t}CUY^*=yoA=Z-oM?0GU0un!o0!Pl*6kZ{HNFL1^0((;Cj55!$` zB9eZdSq#D{oB)^4IXVXNIbuzUYv?V$TADN8{9eO8*J(dfQ9LQe@w3qWaQMr*PW?Zx zqlRMDw?I1$1eqR4fM$ zQ?lnqnt)ld=*6u}$#ib0!r$?6ffw@R1y z%)bu_wG5yXyjuO>Qdn(TUKYSwWNIdrwm^0HLQmm_IU{GsQgI>4AFj98Umx{03x(@# z9Ly0J(>6`0~c_Vt>Y)~}Hezt0Fq8RK7qYAJTBAi@!u0#s;{GFX;?A|pABVyxbW zCTK%0U4ndJ5a=8b>=YXMj^9H4{Zic~)N8DYE+f@~iTmoM7tXCk8QZO{{P%8;^S@_m zT+yOMtS@VmiNy?ptwB2Pe~H`sX$s7lwItg9F85}ZqN3$<=0-Z zLeQjY_f!hNZsq2!EyBHbJ-YYmLAZM_hAf)nbJ8U~OlBlQe%YX1rlOY5jE^r>hIR^U zDkWsD#rtGH_ibLP!u^8a!2k;8y-bRzGN&Y1Q!2MizA+F*02?NGC#H3dDx-fJXE zyB-B4@Xn-N2P~s6b_M8@7N2-lq#lMkuCl=8EV zY8AGq=u_ETGkfxd-u7M;{t52hxPXNwI<6in->T+g-A3O(>9;MR`|OeQySr(E(^qmw z=zbKl^0}NeK$qyH=R`@UG*JW!0*(VHDdP`F@e1KD{ zkG^#PncS211!k{?8!!?2Y1DeSL-t^Xup%6}C zu!Z-N;L0R`r9gwI;%gnE#z>pv9%L3Qy;Z|Zg(D~bz+ahs1I{lcCz^WBUdj#@#(M6( zZzgp)amd8Vk9?azNra$lX?1+7qC4!sWF(2139lH($Px$*=Ut=jhisVdM!1g!+?x}K z!&+@ecEPV;M@iNb|e7>%lXP z6sbEOOSPO;ob|6IiEsmK2c~ijV zy>18ayO~t*MAe{Q5^DXE;QE%`3%|pwQ&zLiZ+yLHRXv{8b*gDQbo}=!YqD(rYbkQD znZi*pQY(!lM?xF8rx{1zSqhAU%#$`-DE~6ZtNoX0}w{@y9t`@(WA-=`Nn?xI`nqTO{c8!q;bk2 zrq-)6n0t*#y^H9v<~Z?1ZdTsa1+Cxto4mu(#?P5V3Y@vOPvlkAf3E4SaJzC=C_<=V zW&ZZHU*8?VctEwTFR4dmt_X%>0}d@-07jksqVj#{z3QGIDu8ibE<6{kW~V;m!(|EA zf6wNJZDlJmxjjqXpH)7;sQeJm@1i#H_Ir@Y-J`LK&*3>dNugF7L}`s8L`4ZTVFHeg zGUpQ)B$Jq59~ME~*BIm*_Y)*v@}$l84t;XHpo3aNiN94*4y`Vw^`>QShw+g_N7{5A z>HN@rF0GBL;Xj}?Cl)((`!tv(OhHe2SSmm9$iA7=H6XDL=S?a5?G%4y;)T==1FWJ6 zn{@7K{{EQ-*A?xx61)bMI(5kC&-`kg_65^3IOVs!vrd!{n48<=KQ}!vPLvcLeB*3i zk7w^i3oj=;n)FJo7`*-#{1Ju}&_QSG16o> zY%6g|ih-XkkCOf%NPfpISD?b1HvV^L6Vut}RL~KfbU060__yr!A@Ay2;B9Xzr)zO#&8Hc=1E<11PBNmdps2scQ#Hj)?|?Lr*kFVA%G=ZDe{$z7P;_j zW^d>mP&D9nLpU4S*gW>%gT!6f9m6{&Z|jOL8`OWgd%SU#E?_MpsmyZvVdqPhm4J{l`(~hh3g-8Ar#U7-@ce+|pwg zarl->$-=i3XPnHYk}mP$6No298;eB6OJv)LmrZ1XVQokaiYewe{KI^xMy=q>Aa9BM z+d3lLvdtFV?xj+VIoMR$>d-IkouGyp5*1# z{{gCO=JCR{evV=f%Q>)b#;1|8a0d<(@2OEWD4w!-M~lL=!#QTAZSwZ`(=z#6G@he&t71 zV{Gh-Zo<{PFiGGu02gvgs#vS@{O@w;3bQ=5Q^?A#T&w4Zc5tu1ok3lV3P5VLKkrL2 znkBMWRKHik7t&n`x_2nv7(O{ugW>ic!uP7Qrv?LoT89FH`R#gE68Q)yAR(GrgD8Tg zr&&C+g7l_4rI3SP#xd}hBqBNXqB7NroiCT$`~W>pGYzig7IeJbR=Duv?Wa!FzrCe4 zNx+SH9S5VJ1GMSN{?it&W0K$~aMLe^BlNcNlSn8J$X(8ZOJrYPoUSXc@P8MbeC#J__Jd)ewn&n_FSr6ya`s?9g{_ry^&G?h=1M# z1b`vjZ!kXg;ggex?}2w~-I1h~IG5m?y{Ze(o4KX?j;*a9>U8gn*K~>-DlDKc{Tp3= zF&9_GMN6$h5+{@EKI8Fzlz(z3($?l5QxNfv?#bd{`cbLJJDs7SmR^o6dQmK)g0EY$ zPjMBWT7;PBSKQx`Hk*2G8DIzaV6spj)OA6>ustz)11Iy3?Y|%+;;n?^W{qM<^U*f} zkNuiy%vsOtC~k*`yNlst^QY_yx<=cHs)N-&QJWDL9BnB=ClJoz;NJ;>S@YJ;(1X7y z-`k{a#X+A%94-0njv6$7jTEwA<&Q8JL{;u_>@IMsH2HcZqrPXO^bLE_!2-|25Nber z<>#WNno(XWNjxgExb6Jrak;!Vsr%ZVLKl|28r%>ZShxLutxIgapZ+Ov1xY zv2|P}<}ACHOg=?my31F?)4%7|+)ry)JRwOUu>v^A&v1p{bz?k1YYo!dsVrHP_A^zmiAHCnA?7xNoGQS)t)}pZOr|_v6%tOT^D3M1{>&b~$c&u!9_zSf*qWgA*(i7?gB^cbJ0UX6t5p#n_j z?1+oZXN*=cqtxwGze7G&cb69LYnQs#XjXa^hidLFRX=$CnYD`4z?YE9ug?zBH`%l? zAbIu84@pviF2@A7oqwiWv&D*0tHp3Q=!;mGR-!Yr&BwWiW`pqA;wcSt6vO#?s(3`- z!W+sw0*(*idARB5@aJP;dGcsX#FYcgm@ba=$SL2Q-_hp~ zoVR(|*bM6F=v14Tn$CtrM7S{X@h#k6eEasy-PxJF+UKYPM}f|P-oFlp8~R~Jkj7LK zwqCQA{zpV8jhjWjJBKm^6ucS|85{UKkplk`FmyMWDrSW z76gMW^3|B5qvQGYwVT1pygA$Ks=KxI00Zvx$40wbtI6@AH6^oRbz+4C*F1Cfjz&{Wz7((rG^Q;4dT~X? zJ13om6iDU6gAQ|oVtix85nsr3Bre#-e(%fLV@2oQcHN?8L4n%|Y|#)|dSR}H>WhKS zLe(fvu2_;otHbs=RL%HTZ+(9GKXkz>?kXxiq7FeuMmEOet&;F&m^lbur5^<&hoKNM zL_YncssuzfK6Q*G;M@l&WHNrq`FgdC-Q&^)8xRI26aWbg6OXW7PO-wjbz z!TBSM`s^hN$6mE+{N)1J!wlH)8UJX|MJ=sezhv?%5*&A$4Rk8fEgazTII?xF-E zPHz|xtoc_$lQTdXVxO@W0&-p<4#c!pSZ3TDX0irTt5!Q3z^h|_%yIu@iv+TiLr%7| zxZ8Epo)EFs`A@Ok2OWY1jzEBjQ<1OTRXEqk8^CFo ztlloTObMMLcVmAn?Nc2O8mePFP&@s%_s~(IOv%AcdV!c^E{}vg_2Js*=HD0a15l{H zGSaJwel5hVqApH5)5V>CR&Mf*v`MVt`-%qFxUErg5c<*yk;AenanEk$IMW7K3`G-t zT-`48c3kp(3n2^rF0@_efo$qO0v%0)zX_AA3F_V>%nnzkAAxj zp;v1L4i3k;dfC1qQg6_5!*4Bnf{XpdgaFF?So)Y8zWyMW!f zuUGD>v|vn_LFaX8*}hvc_9r%i3Bk*yG@Zpi=m)~Zb(aB^FZNf^UjN;G&3T@Eb-rU? zxcZGzt7*5uWx-oApRBlY57WwNjIlOMTfLzR-8sWS2eaWeyRY4LDKA>pi~UuzRe0y) z!3@Jt9Ptzhv@J4HndIrz^}xlMHxq*1P#-8VWE$)`&%BM#*wx*QV)6RQCj-NuLV|1r zp#`+1y8~C&LdeOEmQr+homRRpZlw0x(0BQ5V6udgizKe&hXfLf$)@Lf!ZN|CWnMdU zseg7$^WjI2oan#$24Y5~YMd9HWq0S`N)T5=1j95R%2;S0b*= zgd%$;BqaPZJgW?KeNXVgYS0}U5MKDQ9&kNv%iwcIXmM35-NJ@(f902&D$^E_K68WM zZU-~B4QxLpaj~82Ds?bvoA{emJh;Q;a`9>o6TC1}D3^>ci}lD$U98wc)G(6*EoY2kCidShnV;&l;NDEc6#=>U!pXX2+K&MXaJ>U zroqtjaz<{7y%G_{3^WkRqQwm>@-FBEXRS2ze_%qS8Qx&aj3)Q5S6ibw;b@g_KSF)< z_ZDVmxLn5NI8CHs>uoj4D0d&z1?}#Fr#sVrE*e&^3TX|X_%?5jO9@yxhwO00^SV~% z;Qu%Z$oCwM)@du|wc}Dwm3)^efCOi}7g`Tj3XQ9O(#~vio+iA8W!)he?Vp7?M`|42 z&>fh-|Ath2kd+?(Ey12npB~`MxZzPk% z<6bhCEVgpM>f5t%C4dDh5UqTJ$Rbya_|Z8gaIF+V$isLDav_4GnHxmeaPKX+$QIfC z{DqP@3C0-M+zJ%=_T4L#6S^@g&^naVz9l18(YGz!`DFbryp?#StMbtCNskGgm+N#iQU+1J8{swaY?;MnGoBFt8Yh1|Ekzk6Sw~?cldG;V zHyIc$DMa<3m|->H)5cO($PNr*-*wepn-`h7A^#WFR{aa$fbmFtAyWkr%#_Z1WT62< zuzAR)M58zf0o_`P76QssQ#$b1fP8Up24gqiyqU2OE5iO-e_}uKu`pf zE{7IDx??Co32BrDk?yXc1qA7o7*ZreK)OpBq*EFs29#zbB)>iQ+|PS|?{U2TQ{lSy zwfEX9&vmX3V@(vR)&2>}00VT@otow?e{mVRe1sDXC1mMUVYAT@q;ZeHNSK-fzZR)Y z=H1M0x@&%+CdpClaN}L9t)av>EwDx1np(6mDHLk#C3D&O7I0}g8yne^lZ0)0m?0v@{mxWZmVo-2ziwCRH@^z0Xx7*1kV^4Qw?LH!gEIN8+4&S~ zzRUJcE|6td?2llUhB#CDBNhQ`PR{YFcix!8cLZYPq%w&LE!Y}|&q`Dpmaea5?Loo8 z@27j_OC+5FDik?{FhlucemvQWOa;%PkAN5G9*LJ$^Yh^0<7ASeFn}FJu-(SJ1iHOz z#Z+uVr&MckQz_)yN2FSZp|#r=4fPM-hrw#H39hXK2z-e1X1pas$DGrt+JPM709_rM3Ge*|P6~UHYbsqoS$Qlkt^m%QF z%Kq}nd=(lbo?C>Fni2T{spg1KoNS=2fT`9lZcGZ()O!86dgw{d{=v&3(d!aRDfaZ^ zsW_gR*PSrpdoSpB!psbxu%9aG9r6=c4n1TV`aGiA{`pSk%Eviai?#1#_5ZVBJ^Rze zsn|zeVlME|d@F&p-*Q38+nP4g+%7a^OV|0!WOUE-6zZ{Q}X#w!4zgh*09 z?_zsc)z4Y0;%j)C6|T&v2m^1Jt83NC?bOVx=+n8ZFjFr{#>v62t?V4}7%Ke&E%zPC zX@J6#WfQetn0IVEVIf^No4qw!1uzkyQ^d&zBL8@!4f>cgB6Sq1Ae?e+;g|WFQuSvo zlat-z=Golu-^i7v)<>SC;9eFWguN!**ag0nCNd+l3x_NMVO)-6;^kJjR;(#<#&e%c zATQ?b1BNUn;r+a^hX5zYaGH5~{$Oya&~b$bJ@kL}g|UD=x6p__HQRop{*ABDe}pOo zEGuk>%?k41uDAH)@0?Sru0@B4*4E;`=RdlZASBdyTxTF3wI+kS*H?wbBeft-MMaf5 z0@c~^v2MdFK&l;Nd(U%B=NwUi);rl58}+n}ObvP)%Z751ETe$jookCvQ5 zc+7=JNon+c3pOSvLtJ3fBlREJ?!9N<#OzP5>)hN>Dk2c52-#wSXW^)_>EbG|bvpV3 zQ7}mju8JJk`;bHY58=||d`kUi-feIL*5)ynXcrfP+9<4k5w>Xn1nS6x;XiEX*}0;j zp&7pUwRv&bXzIS3TRP6J=bAi+l~%c!L4}lD2)Wf9Ru?C_v@6A|z7U^pV)F8S+;d3r z{zS0JQ|ffp_??KAB`a9I#VulG8G5Y0@6BR#1Q*ubuT^yRdcmVVJ)R#*c>Y=@?0;9v zt;5X4fOs1-qVfSTTGA=N@0JB51Gi;JgD85YzrUxUW!$m}z({kW@b5-TBLjYpL1V<+ zljkla`1wCjA&p)N#-vH`ozdgMnF%sVA3wfxjI&5rx3Ci!w>%U9lWaZMOvm;VTIJvV zy`LArG4Dh0f5zh}RgBK(KkdV}Bw+@eu~j zW=0-d(RJ2O|L2P0v}KSXzcHKVDInR{-DpIWI@n0B(+jMwB=@aR2-wz-u<}Fp13}%S zLmUv9attEBVbnG*T$=_XfC3HwSfy`9$_MPjQd%7{gG19kIv#PU2-k8kwzAv_%9a8EKT6{n$zqTgu2P_!z!6 z-D)WmNpQ3Lhh^3@ZjIN;#V6nYyzIyfz@h)I$4ewdej;RxE;uw#cuvcl{x-wKzq~ z%wiAmyRjR0I0&0`_66EP=Q*_7L?-QZuEod@ZySt*+S8Xs+FtpXkYaz3`xFh&&DDE+ zVG{7h2e_~_s1)v!Z|}}kfza~L4)% zCV0bsT{eIohR7o4-RDB*ckhIo5Dy<3K_(`DPLFL|o$sM+i#4`upwC;3^>@@lECJEl z8Q&XcBc3~KSJ(qJmiH4Q(B^MQQ_4bfuciRN(0T01l<)jm-ha6%zsb+;!Lb=nE3;wmtE)FRM8%Z6 zEMASEuGJ^l@$SQqleg!0aVh0LxFNHP^*Uai`CP2h{4;t*QCBZbae^ya>$`)9dx@1E#b0B`WKj+Up}5K~d5PlCQJgKFMT9bsWZ zCm#u(t`1CLzlzz$hVy9tCCXh8py4I&4h4Hg)PGa|v3JUT;xN_1mr$D#5N2cmlcnQPx(4HQ&Mx5Rp>b~vcxgM5 z>u3zZzN}Cz_{pig)HBDtNfsGr!;!x{PK@dX+@sbXlFW10!&^F^JRV7JIp;5)Eh;Ky zjb>nUoq4(h-Y2_*k*LV^lcb7`(U>!7vO>q{Rr?nGn8+_3UV=@jwgQ*M4r#+h`sqQz zwdm}yP8518S&jw}DaJOGHfxV>7pT+k3M1YE7*@opV-cIVA2#>{*#mF?IiPl)qmrU9!5nkLD-G5C4 zHStI?m|qM2Twz5P(X*pzQ)ELG5wZ5ozr%w3wP@V#dTxx&T^^c#`--oym>*woU!H$6 z{UZe*z$jg(2m>)jCm~7|zX>C10n&3o{IC&y$xf;sgyW4WUt7w(U<{|F3 z89}{WINf;^*x1B@sCqRR36qre@fF$dg}OSGu1SVc9Nxtg= z_$p4%=g!O5lReKorU?n?*$?KlFr(bnKCZ7z8;f3l>o|?BXBwjto_QTz`+=6Td>iD6 z8k<%+ijN$4Odul@PfA^2Q)foDH(7{$p`S&t`~rqF?3YckG9Id?qxOd%=~{F~p=$-j z#kO~&?;2X#WTJW!mab;YG&N+AippbquSuXiUBp@CFPL~}rZ8m5QO*a6CCU#zz9yosd8&D?3`~EI zTGi10RyNJrQz~!I(_v-hfVV}itb)fBV`cPTu3&Dmxt`2Y{azmm$S*KdUY&}ziN#|$ zRG9OW6z>NcrPfLb6>(6z0lFQ9yhiqy7TCgJX06r+XEvy0=mX6+^6y}(HAB^DQ^6KZ z=OFFzFO8S75*&v(_)RQ66x#QBzp2 zlWU|9=p&9l9tx*+FaiyT0u72u3=M;V=ByrdpXqwFf20} zE<|OK(4iy3MMqvxKa&*a9|lEVSl3;z#{?pG0sRiTQptYWSI7P3Hbv{qraG+xtN9{9 zBvkT?;Y^uN(!KF!yca<*qcUQuL8oGQ7qfIs!dc%bM)lvF8y>@@eDZ-k(=tNeuf5qM z2|v^#Ln<;>4ZDICyMJ5}4kX-$g_~8KeN)>)ge<9lwf~fO+YWNd9{0lSp567n^rG|Mfc>w0xg*>A+Z{Rl^%PIF%YrY{cy6X#GJdN+#4P6%Y8O_eVt?XAwoV#v4@A1ag*$|C2P{x zfE=hr3`NlR<7V9Jr*_gjEKXD>nw)pFHA&K$|7Qev(z$*mX_#Kli=ujsQhD)0Gb z6zZ{biS6eUly96$s!B~kap%qv_!lWI{H{8;>b@w)*2%&uJ+<_W%z7R=qC$WHc_E^Fv2|n_#(gJRY;Z?4e!lV6Qg8vt zlt4Rq>OJX|Fh){i{_GiQke^|tl1EHA{Gnd@!asGiG_2Q}EO)cV{HQ&)=1&rIQj;(> zj*N*xEfuY^!f=p*L?zb>E$aE65$4%Rwgx%jDD+)mb6wrEi%-#a-LJzE*FO3DDvR?^ z>YX=~@jolmee`f%DZjL){oG!Y6$^W#y7~?ACakr)!{E3~!{cprv!V*)kMCyWOR%OG zLW1?i5Ya0)Hq9^dL!FI;?K4+VO~YHKXNDbu%XvPL8}c0|T=NubtVyFC0$mYmTNTF` zB2nth2$^q^Az5AS2q7DIWc8B8=(WZL6q32 zx@j2Qq3TWZJSoc*bmdLj3Rd&q0&G{xyj(aAA4$Y>(tC=atRu#cZ}~gWMRADlHauJk zi>|U5c8Dj_bQ{PDD7>_zT9$>*>4Q=cq~0N6!QdTY5$uS zHmA(f92|;lPMtfUbicDS%Y1w&e&QHVpB^-bHIKis+=j8n55DPGu;k>pv*M18gVrS} zWtXYYC5xsdWr`0qC`~Ya`#M$UlF3_EGJ&?jX{;H5qp>p$C5x z)7N51H;xk@Yx$a2aU&bsyOpWCU0IE**e?4J&pl@W(a^Ob(#SZ@J3}$wCSTNhzSO1s z6DxfpzH?@nW6>in#ta)fgEvm5jjNld^?}A8BL^XN)_r0u13L_g1%~ z7Y?ar>$3e33|uix$*C~q(l;f!jj~t362-y`)XNJB3g*Ts=aUR`g!UEbb6y~I{q|zUGXDdHXC^>SH9fKkXg`QjfJM{|#CAsK)Dv_Z7)Uq`$2vpnp zaFx-Y)#~3SqGN(voa`?7tb{Qqt?2V;*4{In9**E_Ib8{4dmH#B=3hJl=i@S>&U77v@OG6p5Pu)N5&se!Kaf+B zt#Xmu+CW5sVEfOqxZLV8@bJAKmDp&Y67CqNw^~9*Y-n9?-fKL_ApYl@GQn;TA3pv= z5!d0hHPc=GVJ=rm%EKJJjZ^m`jjp6{%@JpcD&pSic{-aw3=Fw!KK;F*K&p}<%0Y*6 zk*&Xm-Pa{)t*5P(d@8rmMq0IxMW-@nrM51y&+I5ih~LHNv<7S6{>MQQaPNj1;thSS zHu7R=(i z@1+HgB{A1>w!Md?%PYF0@id3C&ClG+6@1!CSCl>33B+K@%M6+Yy{Zf$ov>hotC+i2 z?94m0FEVx&c2!*!s}%d{^|ec=}bK6xK+Q_N~7oeT-C z$M-H;*=ZfJ?Y|RC=B%2((+{Q2Z;u|syO<-`FH05moPOvu&GbgZzX0|Q;8X{~>c7wV zP!TxPhvNpR7Bs$p$nQKgRNjz$c)A3?9Kq9+{DuyiU*y<6J-9=l-RgTDH056(luvmd z<&TRbeETapsqWe9wPaymxfq4A*Qkmxs3l#ZxG3n!cX&7bSw^8W*QeMJ&*iss0Cd_W zYWW3y)VdmfOYzpm-pdz1KZZii2JC7VLbL@K5p8h9Q+Az&{@rwrdhLyUEDw>NOwiZs z3cJ)l${JnSJ<_-8i57Yvo*CR#&KqR)nBO|BCE0acQ*TZUolmSA-&v#M5Is#9s%rRg z*gUl!0;iNb&vCp@{>3juJ8Age`aq|G0rDF$&h>v2SYYxI80#$ZsbnT>ru0AiqPy?` zn!)cqsqD!?5IWP9k<Pye3V+Gvx(#CK zj?RBBsB?X4AvA!$OROy06f3G27W!GgTiTRkLz!vy;@tk~>-Y@EnjOhZo`@FZV$PPU z!KBdN+hYsbM7|2y1&?rWMn8<%(cxyBSg ze_>9SakE>iN*GAJ^J#a_wX{b1);&1e$>eRdwVqE|83F-*N#OuL4a5Mg^A74#Nm+ip zTn94{DhbAkvSIi8dGcU~a5=3&eR48z_Q}!Ou_CL&k>f0G+eSlbB6Fz_|eHBkcQI zB?zHw%i5Z-y(cc|}zYML?)to7Zf6_UVc@R#4asZX6LDT*-<@{tvT5)EU zWY;>vFCIU$%l>g3&G@!LeJ{7WI2~ykQ}#lq0re2YbDICXDnX;d@+rV2d~XIDgTfln zTrM3;)V+2!oEo&$0}?XSoT)jN-za*ICCfaV?f7bGFog?yoV6WK9~YureD#79i~j-N z<+h^xvd2MLbCLRN&+)#mBl;5AJE8yUJ(oexwI7-N6C)+$THe8eeo-pjg9lh-C)ONK z12jVSGFo@O={YDl(U;(SHDzw;{kls*8jjOtMEhq5E7F+04p1P)JR2d&c;j8TZQ`&l z?hg90%JEi#@Ce=rkDbdu;hC-)Y$<2FQ9Kn*R+&wFEko5gL)w(|+}tEuRFBbzC^TQK zrKR63{2wkOPWM8!X86?W`1v#Ns}6jQLdeTPc}atgz; ze_2>O3EVkLJT9j?6`vkF=@NE&&)><@6a`uh?t?au_%SH$E8qzNGhoh7Fc0@}6R)k8 z9!H;hiRUPqE2AL)yPKGK%;S)dH~C2yXcO-|6+2k^kPRv>>||{S#xY`Cf#286UAk-z zUZ5vC%EV|hSDw8Bf)4wXZAOBHZzCP^H6&bd&WR8j#EvVYkLx1oQ=MrF$uWb_Ue8z&$G zu7$ilGTbv8TW|Jj*4D+O&bJtHO+OFb4O?H9M*g_+cPsnD-M_A zP}(De6yqQ-_vk1zs@q*cPiKbsde<^j31uDhLF;>#cNbosOUV};)g4pv>E@g?L#{Ma z`izWfF^Q<|5QCy-`WA0K(`;#aCBpG_%4Wr&Tq3J#>$#$$#Yr z+p&4hj~B0)`|jzV$<~d9GadEexG~qZI z1r3s>d5;AP+wF~&TF#2M0Wu=mtKaF6crZ_EPY-5vnoTcKgZk{5-)kN$!fhJ~^JaucbxOPnYiZ9f=;Fp8lAb#DfgxLBSab8f4L@eQ}$H4H?&h26KFK>r1w znen%?*?RX*cJ6w?PSscCVt4CeYz^;5lv1&`?4w9>)ke_==bs$tD2c?Wm(%{04jZqy zk^nSC9{m+J%Ma)Z8=NE&O8(-z7oJS+5CTAOFS zIxYaLNNk{s`Xq~w#JZZ(CQhWHuyU>g~pUHoRvLni>f!%Oig7laa7VHf`lU&q30aM7yVGtEb$*1rRKC&%we0zzX{i`etU%Rmdy(?!iOAl=2oK)P=uX@gMN5qtpGIZZO3ZKrEg z%?@plPLBY@Pf~3jj6scIA}U-2k^BVgUDJAw7mZ_o$YhZh@A-#4>aMF%Fkfc}$i&|z zGJd=nNoL%2!&e6O2pK}F!YUTXnrq)5tT;siXWF@C$&)}JB3K5oa;yCKHMor3c9e{% zas~O(M}NwP9GjWzP7zL|=A4yQZqxWJu4=j9@=x_>hqwp9V%3mnPV}oFuP|Lj6{VyU zx0qaSw4gQxb#Sz|-tUhGVw-~94ba8gbuSJEv*f!3RrdQ6Eb2>98IrYesP;JDT|9Oh zt1I8{M&6(Ph)D^{W_;9J1NaIh$O|V^4jEf=d)?^6me3-Ma7?TYPnSz$hU7DM%cmHUGd_rChF)iQaj4JRe@XQf}JQ zME)IfWN=N^;P7=j2eIE9?AI?uT~W5ph^^!D9+H*;rMpB2O=@LH6jf-Z5cJ>G7#LKL z6IiHS`^m3TK21!i>p;uK-pt51jHG)`g^D}Zen7LkISxjmg~>wA)poLlw*nZZj>jJ3 zE45V^!LJQRe0rjEfrLq8mM71)DW0IXMs#;{l=G?Na^-R-%K}Su?dm9To;)diZ+hc%#@gvkcm-AKZpbb=kxp@}xeBmwSNXNU0kJ#6km5$@JcIgz#vlw>ZDsi2J#s zBD726ce~;b)V=#pjAL95K8Dp1T|GOKlllEP!TAP{p;jJuwe0XjfS@ql?{~}s?enRy z0HHXY!Er!bNaH}ZKk3`u{rOu4Zyr8MAR-aM^n8OVJ9&8hLsUl{6rw35|1R$U*Uyd;&H`^^d(XslWkkS%2Kp2 z_oKhMdW_w{j@pgbzCTAbQe2`%Gy=M;z6D~0-4{_R*(ihl4MdutzZ|9VjV<|xIk%nv zKJfOvbW@UgP3gNIyP#9y1YoIfW>bkzqlwr%eSJOEAO#92co9`!r%z~ZN zRB1RR)e?z+W9tF|3Y#7>3DqVTH`28D8<8rM0q3(H|NW91Z|v`P`A1_1PF~}UOjyP2 znMwN<%~gNCR3tvA&+#ArM4-5lSHK)M<{Vm6-5O~$y(FW{>UO46&}!?Z^noQgYdq4C zZgqKUhc3_D!JF_!?i&-GD3ea@$RCf~=I#;wlcSF)IiefXC>l*$WM`ofhHt#`2J~F( zg~kWCD-IG>qD9JPFOUIkmYK$J_s}yC!scXmhmV?4^t7|pny;q?$OiJU3Y3wNj*;Xs z2eY~MX;xEE9hKa*0b*%ef1QUML>=4(?rEA9EQttOBCEWoSieQy+Bi7{70^>B-&zO( zt&P7^F)Vfb**$4{^?tr(R_DR1DJCeLn`&WU900^YeeygRB9|7XYmZfQrZ;)+0~7!y zm5U4GM9VY;VIk1Y6emL|kW~20J;|hwqj14|M?J389bZ2pEHtH@M)lY;_FplLIj3U6OCt4W027fL1YX+~=3Z3586D$q zHxlM5&@T3HtEraFmRF9g!xxxTS(HhA4rp_+?5ii9x^d3DYv--eNUv4RCS%ouk0$wZ z)cwf!Qbm17({m~|&Z1|6wVs8@j$&91xRoWDEka^Svgq-dGvHdM4DJzi3W7D z#};xCv7qBdml5_-0Pj6WAsJlkjga~Ms>N+>__Ak#M20?-usKT60I=BIwO`y4{OJ~y zdh}70{oMvNqTHBseBuAp<~NkuOaYLra)YGr-Ue$qT7cX!DuH3GVGBCCS=)O5q7u9Y zgqM6VqAy-}DOJ`@+uA?`m0lM4rVa+l-YdW?c#e#$v-L2?sYzu8;Tx50cix*`SfR|k z`{Wqe_G5%^+U*@17wjk)&QiWyc+U=P7T^MHeAysH2z80Ty>KJuN|EEMVKqg*ATVE z-?ceHb2@YRRXb`6P|9Sqf=6RiL663a4_VHr`X92S zcxpw!*LtnHMLGCx;dNVExUUxh|i|ERofCxYLaE@@zesFJU3kvb#$_(QTF7p@vHA zXK3fqVqylhWMuxVw-(3k%*|UYRFuC?ziWp|jQB?5GKrg{m3qvRteJ-Lsnhdw;i=Mc zXUcfh7%?-C6=D6r-m{>B&Ld@?#){Df&q*_%Pp_=3?)VAt9Kr+&uHvJDXlANjTA~Gg z`F_%7-@-Y>EtmV=4C97_tsAAo^sOLc94d2ND__L;kFuM~_NV)N_y8U4&gkujL}{+W z8Twnx@(`igOn2|o@#f~`)76P**IxMxJ-uACgX4uxl7NRUs1e4x|E5Fj zrd&V6L!ha)|E1JU{8nhaeoXemr?Ae7`-!ar|de^LAYEvJh{k7 z#$X%;#5|dR{3%z)D?}FMPO74E?>wfc7)Kr1)Yj15=pdeBHoaKIFtVyyP4e^A_Md}ppi$d3$lr59$$!;!>O0Q z5;lqH2P7<!qQ__FMoWEQPc^>=)L}Tiqyjp&mpse0_LK{bOtz$66H?LgX$% z(F$=L0iQpaK3mRTN@m=O&6FS1pK5@;T=gY<|oRQ}@;|Ivi2gl?2 zhKq{dDhG3wf3ktZhWn9LB4$i|kqx;p!_@~|puY-%E+_DA^1nL%;W>0p$5O(gBW1y$2_J`b-;+Sdu?e|@sF(otok~kV?NRSp%K;s$B|#r zqO91$aQpjXd}UpGnnrg!%^{yJi+k};NC3(Gj<`G161J$@yP#M(Fh2>*VHnGYNm`$S z$wweH=uEWc+EK)@EaS%17)O#x;D#~m=Jo$zW74cA#iz%3Gk$jYj_Y_mFPWzN+-C#MXQI)3x0U2_!4qy4WAML==_Fa6p!I;qzcjn+ zE4i3OG)=9=cZ_CXP7kWxm~rPbg&5lw77c&O$K!vS+zdy7GvmRrZ#Z;7f9Y3AkMPby z1>=Xr1q$i$rU6Kv2NSo#X_JECNq+gqoJ|Yp;EiBss}bm^EE@RB`zO7>2)D9f8m)w% z)4~F7h1c&{I0VX}zD<(jsrap%A3_@qT=@3OL-dB*WxzwC!wvPE|N4Jg4)Ae1w@|}D zz@LEmHDA74Xa}Qq^w|OQXtW#uzqu-s7p{uIzAlfmanO3J-~aoKdn${v45(kFP5keF zF@2s*g7r!9j@>OZocj_8O!hXy73}x*4xrfVbj&QfkvIxL3y5_x)g2)&#$mN20tmQt zRXg)r;ODs_AM&%f->&Jme zhg)LDPIN$KZ*|UOp`Mcs16SQr5Or11&H&S3iJV>@^jTI}~w zu_~e-Sg?vtVhD3ut}9Tpj$eYGG7pV_>A_X}X0i0)1~uxYRmw#+>^STjTwD6yp=OeJ!QAw^J&1 zJS6PA>AnUUPANb!Vh9^*o_Q8O8@KpQyL1HTSfr@E9}Qr5Ew3L5Xr(8tL`z*8YO$Kk zUNo!4;PW$CV*WJ<2(JCM74&5;-))R+atGNu>Q~^)u^DLY-EU8@-z3mDZthG~B7fat zZ2ZWv>*(2_aNh<$>Ar|&Ubd=MjbK#3oe)0xsR{4kYhQg<{l}HR-3Yup<-##mLweRa zE?lBiOJA|8g&cpeYpv|7`tj$!QeONo+5vm_SVrhs<+nz zS$;SrVzuo0DByfCk|X*R>>NYP*ZnaXOW4U#Q8(K8+QzuAA#_umLR9$I7UrpC>|O&@ ze*o*fES`i0@Am^hzYc3X;qMXZ7oL4dK>UHcV&x6>gpymrpGK?>6P^7BF;A*F+oSwKBR2 z5zk~x!zN+sW80VBeC#2_kFJCiU{yzRD0dO>#od-UR3H+E3mRQQF$c&qN zadpC-d`Zf0S2569D6eay6ZV8XX7s)xw^?8e?3OPCtz<@^VC|IRm)UCkVN>}EVOCPE zr|{rOX1NpP2sBm}vsm8Cly%dCpW8#;M4v-}dm&6IDuDAH28{}2Am|kf%d30wjuAwi zUxrE!<&4}qO!V$7%N2DTu0^zD|Mh!zwwoAX(y}7h)OlAM8Bhy`y{&`~pJk4XuJ#>a*EAI01aKc{rBr(BNJ1L;%O(tkyHWc z2I}xx>P}>d=^mapb6%S3)L(b|v`DZ&)HCps1mj}U_lD}B%bbS-;g2r+TwlJ+(fcia zHYkj*qZdt|k9I!|R>roVw+eyF3LJLlT12?TBq#HaKNtc~`I)lEGN`w~`W;1T^Ri~e zDUPO*<`AJfrMADR`@;H{1N-F19B2|6=)7)kXpG;J9!L7XhrZ&ZpJ1vU5%N+kTh4w(Nk6L|F^QKp zG9G{H0gg}Z=3>@e*xqp3{5ia-g>?C<$nrVo*E*kJkIsUReGg1UrV^7455V!&CM&*Y zK?@cjfDZc644NG33+)^|6$pftAwI$)Tc2E-%l!50Dr4`-ot~KJ`pwmoz>GASUlx4! zq*LFkD+9iNYN*p7x25^*cfA6$J1>DLw_5lwL^_w;DZBhXTqFa6U7WSVxcNYDfaE^x8HC)-9V5!7uBY<4?Zd}<1G=kj{p{B@7JXgx^HL#Kala*^AjYk zd?vX64H`RKMDF|=DS{rIa!Yb^BZ2nOH^p8n*(ZMIcY$sGyFGl`2(Kd)KupbNQUyU! z%PNmWNC|)2gJSqcGTZ`cKElNjx(L>AlaQ-jYNclyXJyG$Xc&0O4I`&%JLZHB2e5;W_cho+Fo z5N@?YN?y;_xJs7>5|Ks-;n6WBH#9M zx_ggO27*%2%6s=3pYv1P8&b>Ms3Si{nYlL&CnRygR0s1(c+Z6sKVUl-+;?NL^si`2 zW_`kx?Q&z%Y#{^KH|vrxp$!Jf+HXMegv6KN_d+&)rIZ(`MKh})+T;S3n9LxGo!ZM5 zU1b=gAO5yR+lWK<5r23H9+9{hKf2Az@uNKC+noRl+dJ|1EuKbKC{)6@2tUaeJVmg7ty8|u=I{!Ny7&6 zxErC^=|0(g@)T)ES@p9eDR9Dh*>_kET`LO=uLze{ zej=%ePW$I_EarihQyO+9n1QSlv})6RV8p59uO00S?XZUwW6x)0!BoQv>s089F*-Gv z;Yn>giQp%V10+sSRRWWxC5b+#-+$>WR^@^|4c_2mlarP9SvyzNAX6me+(bQtEKI-UxN)i=|Lykm zC7E3Iley0+maH*LS{V1v`APn~(Co}37IVcnzKeFrPY?(t%U^)T;x21Gt@)KQ_gEkW zJ4lcKku!u{)u0uGV*(*o47b4~K5=;yqWt~NwykYQ&S7tZ z$|b{8<012Un)~ZS1&M!sa36^F87m^B)E>;`1@u&|)o=p58JLX9N?bg2SoLOgJ208z z?!Paq6_4k8o}EX_f1*W>wG(fxB(}Z8a`JYnUz{4d^g@ByC8F=;~2B7NRX+ z5F-k`%%0U&hq3Bn07KxhIkZND8}@{3#V+fj=1rGr9akDYuAv<^@Xu4Qfiepx<_zl8 z`5U#Np5BfHTnBp4_1$m{&o@r;|M7>H;FSF;vWE}t@4IfFNW@!xTCYLZ_hrV^MtnqI zZ&m~do+qa0@m^$HDC)*+d4lh@g;-)*_cPzoWW-)T>{)B+EeFFYbn_dO7N_80E)exH z&L?*&5DZ?F;c@DL!2cfzN(pXHd^D5)(TaMH$E;@^Li!FX;4Se#8OBmBvMNgmFM^+qK3-3~Hfk%BN*n+2sZn$UalbQE% z3V@A|10$8QUU4`_aIW3seZn;SH3Bv8{qt8b2M2kIdM=`q!9E9!qz{*ZfD6k-ceF`I z`)G3+z&OEpJq2yOjH9JVp~iOk$*>6ELrpa~-wqZZz?eTbKkGH?T&u`+G3SGug;jTk zD{CAqO)ybx`KWNRax#%Bo@+?PwPbI%vRJWiq*KD{c*1Yi$2foylhWAE?Gj>QYJg1F zR9gABW%~}@j11o*zUHo)Lp&KZKg?h{4Bf&#&ToV9--A%2Vj?7MRT*OG2i}TW1m2Kw zJjyA!7Xka$N-^4mGdh~M$1I4HJUdSuJ|<-gdi8+*{U7-#sbx|v^QLGlZY*%nPGZH# z&w(UC(`&-?w%UV1YJFH7w-vkpq!r#b?^1j(XoB^Xtct#so0#vWiPXcow_&;3cY_7Udp z^qw!j{U-bnG8OZ@y|R!eW+KlG(LT@MWx3+l!EN(B-)p~Fs^6_Ycl(#?HD=c7wZ0WO zn-6_|r<7lO)z@4yWBWyc=OH0qZtq&MJ?*E=DPU=FMC!ZKU46lM+{gbsT6(|SB241w zEMULu=D7}$;_FK$-qZe5lM%I9b=u>TE#M9`(6gY<@QDnpy<(6QYCm~Y1bNheqR`l| zMv^HPbS*58BQ)?q#xWe=1`cSzfQ=(~tOCqEAWS`p$^YdaskHI5&B#vxneFN7=d#Wz Gp$Pzf4ryrs literal 0 HcmV?d00001 From 320fe38dd2c21472131734b0b610bec55d74204d Mon Sep 17 00:00:00 2001 From: Chen Rong Date: Tue, 9 Jun 2015 16:13:47 +0800 Subject: [PATCH 40/50] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f2f4ceee90..7b98bbcc2a 100644 --- a/README.md +++ b/README.md @@ -37,14 +37,14 @@ For more details on the PowerLyra see http://ipads.se.sjtu.edu.cn/projects/power We argue that skewed distribution in natural graphs also calls for differentiated processing of high-degree and low-degree vertices. PowerLyra uses Pregel/GraphLab-like computation models for process low-degree vertices to minimize computation, communication and synchronization overhead, and uses PowerGraph-like computation model for process high-degree vertices to reduce load imbalance, contention and memory pressure. PowerLyra follows the interface of GAS (Gather, Apply and Scatter) model and can seamlessly support various graph algorithms (e.g., all GraphLab toolkits). -![Hybrid Computation Engine](images/hybrid_engine_.png "Hybrid Computation Engine") +![Hybrid Computation Engine](images/hybrid_engine.png "Hybrid Computation Engine") ### Hybrid Graph Partitioning PowerLyra additionally proposes a new hybrid graph cut algorithm that embraces the best of both worlds in edge-cut and vertex-cut, which evenly distributes low-degree vertices along with their edges like edge-cut, and evenly distributes edges of high-degree vertices like vertex-cut. Both theoretical analysis and empirical validation show that the expected replication factor of random hybrid-cut is alway better than both random (Hash-based), contrained (e.g., Grid), and heuristic (e.g., Oblivious or Coordinated) vertex-cut for skewed power-law graphs. -![Hybrid Partitioning Algorithms](images/hybrid_cut_.png "Hybrid Graph Partitioning") +![Hybrid Partitioning Algorithms](images/hybrid_cut.png "Hybrid Graph Partitioning") ## Academic and Conference Papers From e007b33aed7d689118416cc46693c9c16085e7bf Mon Sep 17 00:00:00 2001 From: Chen Rong Date: Tue, 9 Jun 2015 16:17:01 +0800 Subject: [PATCH 41/50] update readme --- images/hybrid_cut.png | Bin 106797 -> 38674 bytes images/hybrid_engine.png | Bin 36999 -> 28103 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/images/hybrid_cut.png b/images/hybrid_cut.png index d220604a87bc17cb1bef4b32c427483efa0ab7ec..34b0edbd3db7edf065d6a84d8b5311d1e1f6a031 100755 GIT binary patch literal 38674 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 literal 106797 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

m#8E-tO% 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

kRw$x+x||J`7yek?bH#)U|4%FQxqHIKllz*8w( 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 S|N0jACnuvUT_pM1@Bag1D||5k diff --git a/images/hybrid_engine.png b/images/hybrid_engine.png index 45cffb1ee68d0ef148f2f462740bc2962eae9335..f501c04eb61c7764e405c53c0bf4f84b06540e67 100755 GIT binary patch literal 28103 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 Hu0mjfwT`Ib literal 36999 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 Gp$Pzf4ryrs From 57b005f359b0978e7f3b7fec8508de0d842d7e03 Mon Sep 17 00:00:00 2001 From: Chen Rong Date: Tue, 9 Jun 2015 16:18:48 +0800 Subject: [PATCH 42/50] update readme --- images/hybrid_cut.png | Bin 38674 -> 132522 bytes images/hybrid_engine.png | Bin 28103 -> 101738 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/images/hybrid_cut.png b/images/hybrid_cut.png index 34b0edbd3db7edf065d6a84d8b5311d1e1f6a031..10acccb7ceca6a1634cd4f3298fde38285103d95 100755 GIT binary patch literal 132522 zcmeFZgIJ;k>Xa|-Q5eto#O6L+!u%9?yxu%Ym2+Pdw~{rcP+3B%kt9ud7k(E z>`(Z1uf5Jm&Lopb<|LDuUp7WvRSpA<1PulT217w!MiT}Go)QKIRviWDEk|yeB;@Ua zU@N5}1p`x`^bTT<_?D)$l-E>&f$^h@#2JmIW>cU5eCA*5W41d zXo$W&Ky{VZ_jpU={QJS$7S6wMfSt7etmmnxqAYCb;>2!lG_B02u zIeXCjS0(>bkBqg4rMtbWr@f0a<-dB(EnK`j#i*(OG5X)1|8A#~>;Got?D5}Hy~)P` zFn8tPV&~-e|0?8ZZ}Wee_kSz(@5z4^`;S!r#Z2_AYlNlUt<61M+&{axIEt&-Te`b= zxY&47s@d4sTUv{9{68uFPfJDrRVl3IZg2f2>pzLaxkNesUwQvcFUs*x-2aRC|JAqu z$$jfwakMv1|1(hHXtPWcXD~35FbXnKp8>Gv`N(<3pYBJm);9hkpp@dwQ~uplLZL-K zAxoiWR7s}($_S?@2d0@X{Vq?4FC~Rg`be$9^z|=haX1W9czAl?6|}dv-Q%+J`S#($ zxuu(y=ZeSWGGBxU`#%1%99=mg{%H1X(a90yalJa z{v3*ku`gF|JIyWET%&qd| z&)bRbT|HCGon=R>I!N?NE`+s+!S;sQA8zeb4A{o_Ly*uA&^}Wkevn939@E9@b&6MZ zbHYi5Us}v*Nj4s!VkyxKV(&Qx0~~G~odVLRRCX4O+E~TQ8cM6qH4&OQi04Z2o0z6K z#@OZlT(=5p^$FL^75&|Sh^#rebSW-y1iGk#mG;tm1Q-$?!heLTmDRZmqV{Nhz->9$ zov77ZcXu1=njF>;KY&b@VME{$;SdSP(9$2C=O4eY**n~@CT#uoXChPF!8bY8eKN_B zmFv|VsL<3C5=C@DGg`T(w^B4bSOO}uL}^{W!_%{3de331OPExy_3(3s+8`>)Kjg?} zTj=LKV5EI@74&{yr0{R1%6wes^||ejeM_*;go>LLj^6zGR)rAnS)r)UNU)J@8(*SY zOP;f&TAm{PWY=DbamUmKZLCeLy!cDK^{RT8RTle=@UKG{I}ccIkEFP1R_%%SIwUZL zhQWS2J5-5~#>PChXNT!+EX_1ZLvE@7C=ja;U1sB@>4&CH)`<>%9$ME%C0@WXcN>ly z&3VJgEW@zFn&GBgX=H}0`54;y+>mY*Xh(}$V>YB6=&&X(wDKohbS85F}vhW!m6}=GPwG;EwIEs6?qG^dH+x2b!=Z}~zHtqrd6U4c1 zGj7YZP!1JWl}JrR1^OEOEUIj8h;>9n5mfQy8GQJ{gWgou)`BEntZ%&0#kKvH^VAjSw`=&Ww_eTcs!u#-E4cpZMiiH& zjpN(#nXH}RP#q}!P@t-XzZOys_WbN~WvLeTEk;bVht5k|neI&p1TU(?EhMA=Na~z|i6J&5evb0_-0m59o18Ad8hO!d zp81ApB*7#7^~1KUrHXg=qk>tG8F$$sOyZM%Z``yA4!rmtc);c^VIvy>9q)e8 zPaKf_=hgg~1&4|iMiJc_?UJD6I(^&nrEe2Zo?8bd*#s4hdccA{Yw}NY#&O(zTH)UF z34z4{;1*osm)dndLvs7gJxZ$K=RJoKeEcu**|?~Wh?mH4(K zvh9@331049r2Ce^&<~*O9EUI5g8*tk0Iv0mxP9T|v+nKQi@p()7?xQr!#VG2=0L=t zn*thrmZH{7k0I2}=h}}khN3s=+5h}t|E>j6r>2FeV``O?<_t#+g-0ZSMWe+iP%X;F ztq!TH{v->X;r_0};IrnQGtT+v#*tL5z0Dqw{db=8Alp3P;T-Qh*KDO|!Gu!VfaK&s zhGfPwLqdfa`Uj*uv5AUstJ>#bpF{Gx`8kig^4E8RNME%6g+qJm#vklOXKG^-5z)iVS1 z9@+NgUU}01v7PJ2$2aT8>%88$$PKi{6C36zgu-c1Srp|}B&`lKG$hbJNBNbM@jYW5 zqbB7J{B_3O?^jZ0%6H72G8N!m8&J`W^4`6EN4fbm!wqGaf^jv`?4lWO%)vHYFG8hY zLv~k#{n^SkS>d1kch&_0n-v+iGLTwbB2S_9C~H?YH!==uYP(*GwgQ|Ug>}-jCc_RD zeue4_XhPDcbaA#$w{6JYciKQ*xdNRV`2jpA@y;avpc0qDWNY^Xa^TF^$6rAs$w?5k znWr;iP*nDif6cLrm%-ocSEe6I^CE^|^H;o2THTrqb`{CcnJDATAs5w-yCb}Sw-rDW zjW$1Ab=wAkn}w3I+Pxen46lnTp}}n!g@tRG+dT1P*X1S;P7|xA1um6)Gg1hx$H-Q7 zK|hq&X5a(2a#^%Qe4r8+(!i^naLeDbTZ{Jr4Fh$(3b#$X#sRuWz?F!v{rTJGB>g<7 zY~_35$;Su*C5r>3KS~^P{8Ueeh^Q{J#U+xojF4*#>KQfK6c^m=h00);dsAmdKcTGo ze^}b#__X)nNM1$%f_Ny7YlP(Y%I#&WJU!TUJ4Y{o?L73Vi#>2*%nxbaO=mDYoltJ? zXsz4+x6ZOc@ZJY|;*yr3$sc}@X-U@?&%bXy8nKETFxEqzowx;#%oAMk2?}iI!Bg-c z4&8A~Zmo}8*Ll)6zWlt#evqDju-(S9X;^Y%D?n7s%UL-+wfnl&ifcl$<HX7eD2x1!ed{T4kYr;;!zM{ENn5E zh99LOFFS=e5xBBtyec1+T@kIWv2b!IP$IdKo$K5P>~Qo^noVF=SVrzs z?yvoNo30`vij2m}jVDjUK-Q-txyV5+i>uV9o>o5}ygSWyovoY4Pp;%KSF$To%Y(1)#F@o%M>Ibn z@uY4FStDaX+?%2;jTUh;0;BjC#+nT==s6DP3{z0y1RG3MNEt;j>}>Tjzi1t)^#ToU ze&Eeo$e$IW_X3EoeUa&N&C69oNIf2T`9SVM3;<1jyySr)gpWZ?($y-rzl2hUeqnm! zu+=^LgJcoHhdGz7qnC;>RV${K`{hvu4o)J}*Ato;(ym59VCnD+1+_trwotfGxO}-# zFkbS_C!^hi9n`VHC-`IxL;9|d#Uom=$>Um!5+(_4{$^Oz(8Qr}asm@1@h0pbXuJ{s zbX#UjWp#8T2g}Tu0MAHVp5p@A*dW12VXtku4^H_%x8NhpW$33SW$qMQXq1NGL*sIo*@iv)Y~yI1A+2C@oWjUgou0@Zr#l;a ziBg4zQQWRmpN1yit(CneJG}hMQ96RrqA91M5IdiNZQ4@7>`w+hG)pFwQ*#A=MusQS ztvsStCADSCZ4QD%G^JxPG>7E zesYfL@{Y_9Uak@AeT$~%HS0o}+WgM3-CoEN6?>fJC84K<01*TFh)KxZ%-VXz0cKx6 z-V*HLL24UkKLCs{b3lVgndGIE*+((hm}$yJ(LQkoRL(=EQOo|NuaNhYFNT>dRiD8| zj==Lo)F!mO4TJ~0E*yD9C+Q9(r?&kvZ$w~s;o&F#5DVlBh?RTqqI7<2v&>dR2=6}F zY_53MTNayzr#KhgkshOu%4~O4X=4`YFEKlo)*AWB^rCDpWk-N zZPhuo*naQe$&a^wn~R1h-WewTZMBPBPjw`PH|B-SPH7*`I}BE|N}PWH^~xIQ;O<`j z+QtB5%Wh24){T3d1T(|?D9w+*wo%D+m8+dm-yOX$-2x&!s|!er!-mH?WJ&KDy|eC@ z9vT3czRL06h~aVM!x{no82>EmWL|IKP@8A-#QW8?S0=Ju-18LAbGPM->rOb9@<-?*#dGWUVDs0JWTvQjs`|tMcgu zG3pQZRqM;07;%>pQ7Tsw0HKaqDKdu8k}p8@L;L5EWkxF$)qQq4!}DF+s2#M z(KcKW%*h*`h++39TwQ*0DGFkG<2Kv35td!B0C^6^;MlAknmG&vjyB31XYA@<8n;!7 zv$}-EoB@khVMI+ddzv<`*a763Hm>l9!g+9~x%z86D6DzJ^rAV?;DGtp4Kz^ij>|ph z&~TT z3Z()=*hi07s3x8YH~9vs1QX>W&Lx&gXD(cC4D0&Wy1?t;#nESNO$PmfEX<(|YpOnQ zMhMs{Yp;I0zIlE?e7w;c1qivkSkTFxB${MdR2-LKU5m`=C}ygL*EK0DymcHPXo5>w z5zmw=Ik)^udf;k}+^UrcQ3qXzK+pm1&2 zilKjlnSnC;scKC52TSR04RKG5!z!M+-J&O*s4BM{T3ae*WCL%el^+@{Sy*C`VVxOC zgx`A931@)<{yX_7&sg~UgSH`EGVYUfbg1{|fb?j!<2q78OZ-9<2|O11S48mCoO^S` zfJ}>a!1?AOLzZVNL-%=09bZWH*Zc^&>Zibl>+!oPs&FAXGzhn$dpvA+Wp)}Nf;4EG zb|wIw+IptX_do&tCufpW?R04dv9ck#v*E9!{W~gOP}3CKu;5J;QZ9Tjt0xrpQX|1I z;wTv1#6P7)mxx43Bv)5c_m|e>#5amH*bmc%&gR19zp4?$E+6fIeRIq!j0a!56ooL= z>Nt%W1)KL*CWtsx3K;n#7+jnEX!^f2hT3;G(;mQ(GXCnvQy0n5Hf3%muyIB%X{ocy z!VKz$)7(}zm1^9hQBs0QTGkDC+XLay2W^E=47_`nv%Ff(TeEqy1uKH@06DCqHgZ3s z5oY5yLgk{9(T)?NdzbN-9rRRurdCfET{DG1Z0AP_upvzy-QjhJ|$8d1wDKU1D1YWs?tM_aaN0!oJdESH?8GEYCMaQotZ^S9@KpodnAYr6r#PMem z&lGFr76#>8S3P`Cf^{RyYD&DX8_)okw&8epsJZxK!FwoXOPUV!ARR`;7Y=fSHowxU zO*D~TFQk*Zsj?l~yXr69s_VbI!nWo1<>lV{EN-&cdQq6ht%1qWw3kg>z6M8cSQUJc zR`%j3Cxji%M0246-l~8+(pk%|g`P@z{XsX^FnDUhC<5UjA`jTnU!m~w3=4A>zS(b{ z+=xS_R(~$7|12FNry!B<*6OoJM4-y*Tw8;1d*Yh0DXxffv?qoDWFmp_%)XH=mQ-;tKfe9ikP+5yDfssM zjD7JZSD5Ye>Ji7SFE+j2LCObBS%RR+Be&?N;yinj98v!Q*XiF_thJWXQbC1BWCtgcUNWE5hcwPzV$oSXNGCa@sr zBg})-A5pfAv9hiT%Qozr9rz##a)^h^q526%cc0sF_YnJB;A5AM8)mB@^p#0vVPb$c zVrfym3tm5{w5VzvwPQ1w3sX%8|&Yy&~w_tU5|JR5l6 z@I-fhxL~>ZY-3rO;Ktb5-bX4pU0amNltN`V9W7k3!)03|J>uEUxn$vf_X{ z*oifcOzhzqIjnb1sCVGh@XP^biQAu6=vV^F-w}Wqlj$O78&$R}a+ty^9*xxWl0wHB zznnI^Y8c&99KYD%+TL)}7AB3*6$t$)CRTGm#j6x}_w(3*Ol9ieRBk~h^J|2QBFR=6 zd#!A8m9}Z&v&JyqN3$ZyQO!P3qLKbRpDi-x2R$B(q3Bidz^DCF`?ik`Bk{yHx~75J zKz=8Kodq=sZU0$=8+LtSkF?g|=Pf%oc-kPu`RH2L$&~DHwF7rVE3N5J5auBFT;@Xf z%Ia9kg>NrQVQr_-j{P^MOw;yITPN|tg%+~$ys-L_I6(YnR8$WtmT<;ja#xpO=-x5G6 zeb{H^go*TwS^E$%dGGpIT(A5kD|Rxr&fD}+LMDyh5kH$@#RSY_8{Uz0>6=n?XUoVV zy)u%IUiZ-bCK&`+tKwwNuTIuyPOu-Gpmz$mi_9G&X{^0jvL5XuEhw^Q2X{BEslxUR z&Hnz}aJl~`u;6VqEtCCrYn+@Z6HZ)Ly{}=NTuFy=!}+ zF22gzCi%O43vzoB{;;4VtVvEArDh;K+Cm9=?Md5{-rmSc7pv@#cjuPN#M`1ngFf!h zO2@OY!@}ET)vm%N-?Z~>d77N562GO*dwK2K5d2uG7vK0y9AZP7lJsC<`YAsT1>&lf z#@)nGX7$jm$MMP3XY){E0=F|wt+n)ppmeo=v#Hzpi`uqHL}T+ zDT|F8*@+R=I#BGX87HJ3iqPwE6K7|8m9phy-XSr0u)FbN%|FoSY{B{#@A6x*O-lS? zqC)Pg|F28r{Tn=25_xqsl|_~8fUmkmlDX=V7n-s)PxX!)vZPMGoffO~ykjo#65O>MDZEK?*tUG+tZx+W zx~B%Mji(x4x0dMfwe#(`)UZcX>`qgUDvBBkqUbomrPXj=L%f!ZYUI?&!)sTiiD@>0 zlY^E%^vU}7$K*Qkv$FYs==v0^CHU}0|Na|C2f4BnL1A?hj?z(*1f}iNuI-zsw8N9- za;vQQV)Qu~tEEKLfKSA$loILix8F}lMT%Nh-gR@fsQ?lfO`QmN(wIG1~V?`O-df zsJPmTtFUUfOTmt7mN*suaWkgrH%5&J8&u}odw#g+G`QOA)^C}T^EjHZcGWiH|CU%b z$j+HzG^F_;n%Qhxw6~$Sd@z)MJ~vT*)J@NUM(#j5}}rH#PEb&d{S(! zlB&Z;$=c?$u4grhsaW+FL%;8ATQhTs2-PGo&i-(rQqa3iY@Y3pmDROCXDOL$ZS)+< zZX3GAIKQbCs`A@B2L zXO6DxfhCZ`_K(ow`822SB@K^x4|@PvIK7agMfqaA2|35u%%2N5G$zDLb@!)2 z>ulQ<$OCG8;~sJB3ZaCXzc}IA`DU%In^44tkbT{_%aw%_eRx!3vuLyK`lU&0RSN}b z-z|+FW+VI*N_@mkF&;IH#n_PX-Z|3MJ)E{DGJz&|u85PVL_O-J8xIjc4V*$GDtt54 zc<)uxKhQI@(j|r61m%oRY%!8eeEmj9_Gwuq9-DkP>4|G4nf}-BFso`jeiaiZL#?#6 z$Z?(#Lg%tQjU$Fd%oj zC_f;F5f4ZkG>+?xJou(f4v|T6!0ojlMlWy3{Y}ifC4Z7^!n_W6MZO=c((_jOXwdpq zZ1G5co)9CdjRBdT17>E{RbL;y1h3{DXKnIU#r{D=MFg?q+jf05AMTn^$;**I!Byi7s&e{}ac zFFROzo=z((3Vk+xm(Ck!#SK$OioBMLeOCUw8F%(&2^U|rdo>E;iWTOzCf3%3qIM{C%6h|T++DQ- zS039@RmYR*w+wquT#e^9SSc&=pO#NV37LHITayZvFv3ykYi?l0k}xM+kX0CUKZ#H&!yadFfx zLty}S&+}#HoZawtP4lXV|c{jS@Ib+l#Bgl z5)J)eFFnE;4gOM1&j!*%zGJR?WQ>N=_?~+NFI8YO(4PyDR$L6#&HhRCLY#!I{Qf)E zq^CEF?MqGgyTcd+3MqPk=8R<^kgLcsz*1Z+6b!fQlPYQyK%w*Z1XvL+%Q-a}A%K1p zPHS4y**OZuHZ9K&(BpZIn3KqaD>Ag@ZN5M6mT04gq1#F~$%1<#h> zim7lfIb((D3X8(7sRq+=^bhpBKVf?_b8Bm3W0H zsf*f(-T&57J=y-{Tc8d0t1{Bp;mdD+^wW_>s|p!P5`VG_ILOdd6+f$LRHd!nS*EJ2 zYM9_|D-Vx;Eq#eN8XhWzEHfb<9K^_{jBx`}NY!od-w*K02jFiO_iudzGHqLwCt8+wBq5d#RMK5Hh#VJz}MNy9nyd(A|q4xEOj3Z!?JTjO2CcyXxLovG3pyJ)}KtHhq;zaCpxD zB2Ht;ROJA?AkZMi$SZg@c=xS4KlE(0VZ-;nLZA1@yqBfi?yACnH*V?7);6Z@Ha`#R zqbj_4v#nt9lRa&Y5xjVX=bbzY$PaQAM1iUg0dR!D)hiX$MxrnWwlBN>w+QE!LsIoI z&-CJ#6N8J)lmT+zk{OJVCT)K2XvxmVVec(PFnn}+SIFTs2mELx2PA4Dnp_-q6)Uqp z?l9XqK^`Iy2z7>R?NAxz*psR;p(^12yolgm@sI; z6$$P)gJJpIdp9F;WBO)0g1)|ESEu;Jdg5+^ie0N+FkvXjDr+N-I5e3y4{J9tx-CtI z7Jc*GPasYe)Od}-6mK2hbC!Nfbn{I*PVxrlx;r8EY8;nyDVibWFnNgK0Zkr~9L({9 zW3Lq-|9y>s4~5CgS5IWH=lADt0aR+!nlsfEXO>!0-yibkfi8CH&!YUe6Is z6H&GtlW{k=K=)VG0$wd=J{Gar!Hqx9Mmd^hUjCVLn%4XQWp!NH2Rf^|@|=xz?tmQA zdDWt{@hq|cBZd;j*IN0kd|sw}74KrsHhkt>gnNKqhdVBK5MaUTZqji7mVM6X}_ z9v#frKw3l<05~H;Ya1E$O=KH$_Lc=lUv*yf0Po0A{Z$1$C&q@mrlb3Fv&92D4DqR| z;k;{1KlxVb)|$Wvx&>H1y2K@ z?nhcj+RW?@z$Xai=#`QY8?1<+@R42?LNExdk4|Q@fT5tNb9=O1D+T?86!rQOG(BQc z0LtKQK%khYuis4_u_ssMAu8B8vy_Zz?f(>;kRgpe-3$v+(=a-x6L*g6r&X>T(?ffu zD&QL6#Q?6VDwafd_TwqdL*K9hMoQUvyU5~ePu$k4{;NoNIB4ezcZ2DJ{AKs6sMQzJ zMz?AM*IRPtdc6jvo;~NI&uO>F&MJkvkt3P92BuR|Z5&7}`MK(7qqu5LN{UnDdKKuq z&~Xc#!?TWK+=4fiOM&~+OEPx9T_+^ULDF^lv$E!z5W}K#>pRq0Cl%NwY{5K%)Nl#TgRqrSP=dgwJlJboSwq<2@0;l|ip!wt z>1wg7UnZlpUCJbq#e#jXnZP&Ad$A8Tw#*IP`<)sLlq6acsmc15fD2-eoG5?f z85jFlHmtdi@UZ!>oRJ{0Lh<#e4m~267_< z-nUh!!rW9+*M_tPV2P~U5#11svyWPCySTu0l3Z=6w}Cp#5?^z!zl|OwS^p6MpSB&v z&~UqUr?w&L7+A&nKKn4}Udx@h*>B{6NFABOHmt}FmVb}l?GXx4Nkdh|pEt-x(%8!O z$<~8*??9aq$+df1psVA-puL61fNqo(sG354Xay;Yq*gkANml#7KtWt5PBP0g9VjDn zqpn3zwY|OIEi$co;?1v1xPRs2ElBqX*SIi_AN0C@q*2_fFYczc+op}Frpr`JLi(e7 z?3>-{MDDoAAYWU)6s+!#bqlXJU7SNVd~?W76ov4J(N6$TD?v$S`$fxIUh2)?1+`8e z%WVSa6whqiHMyAI)E)s2iPrQh_@f`vFXB&^RibICUw7--$)qX?7O|Ctb>NT}*1lr0 z3$AJI?Nl}X_;4dHe7sXyq53FSpvJHXvOwKHpA_#~ISnf-=15mUF^YI0(*f%NxsK?r zw@SV<;%liCXaX`*_FiIK;@hNz!|jLV;4hp4MJ;z7c7osEdrV@$7W}?p?Bj>i3Z71%>X9rG&oUjP#5Ar&Tqh`|=P6xQ2 zHq8FEHr$qWH?C8rl7IaR&MYqs{+<*Q#_49^6#yI5Hn4*omwA)O-E=ZEKJpQ_vxXJZrh( z{f6bte@q^)u_3aYdFp-K^jB@CRE0pF1B&o0r;KaF{yCipjZ6&d$81TCq;&2OFPdf! z@2(Ikns76ENEOSV2b-k1Uh)fLp6+0MlDDYrDDN_Fb-DsjZCB^3k`AL846-F(+KnYe zl(&Fs7n)Nsp3aX_z&Ck(`m_hbH?)QmLFWFV@9V)ezoVj?Bl;p_19rxga!C1oW^q=( z0l9uFZ?B`?iHBC*M1`hF*-6BsWGH`e2q1Yiu=OCz)Fd$Qnt#o)5!SGF)9?ABHQ$aq zdoNt$ZMct~HuMzVO+@4qDjaAlH2Kx+B?uYb3W5_XDwoXI4fVes4ZGo&${_KMz$?B? zcI&1}srx{yjm57NZ%FnG%fKuz<;tl9?n=0dnvym07Gu4<-6OZ!@tVt#L8Z?eWmxOi z7Ej|^M^If>t(bEr-I2%x8F300Lsl*O58YW$_{A(y83WpiqdVGPgZYC1myBpcJmzuA z7t-f08L8^Q<8)c2kR_nkX@3~ltx-N;0F{9;v2#G3lfdTsVFrWdCKQ>RaoINv31gl) z`f5rQ$lKaApo^Bav#}1)MmYi!O-QAYeS*Koq5IY($j&BFd3doJx{I2G7tFt~{#LLW>0JgwxshxNK;8hxAGvVhD6R4(7{+iZU` zSg_{YS#7h8oacYpeso4JYZX5eIK>dAOpxuseX?R(n84BfcOF!hW#TS%=;G?$Z7O9; z^5$G00cs~-v8eT6&tvJ@N)5#^Ptc@l06q~75K`{}LI66r*6yw<*Zp|10TF){eJk!* z1}XA;)kG3>ktoJz4zDZST?bU?$)g>!N4LSf1ShVy0Zs9?K_=T(5cSI-HoN-FMi@wrcP$uOlv14oumrx0lHA>LMGN zmi7rgOMhr$v1&E3&aM|*`#E;ex^3Uzx|jYE55BDGh~S-gB+f^-)n%9%7iYIsj{` zs;)Dzer$?LAN~gV<;6?>aQqD)Mdu8;A+=}3CHQ%baBF9gSvgP$Tmr*G0G*Sg7}hoR z(hj)lejK?wJQJNStf)Mk){6$Y52_lsiH8*Z+Z**(5iV* zii@0SMy&jfNqs+mEYrU(-gLJ)RmOnDQgg-Y`~0VNu>D%;+5ojTW7~uynw|$Y`+Tp^ zFIbzMk`N+VWPn3Zu{J9C*{t9m`e;1qGmrId7?!x}L{y8@iT=od;vUJukwfE6(DMbu zCx)xLH7T&=2lDZfT&6F|C&wd-)0f+oYs7xG+b@=`KhxmGm2p_`FClP-(LGt)D>`1jVGfv>PUE9>MgAI~Owb+1(9ltx{|H^3hGp z-%hd`3D#~~%ePMvd|CIvIn(X1lVymOKJ4iFu$L>)?cXy=WtY*FsQCi6BH!LTrm_dy zY~)dEG{XllenYT5>xIz26<-S!f|YhRQ1jA>bJ>1AUJ>NO+FS^C$?JoWP67s=&4-?2 zeKMrn!t_xX)lDhf;V~fUIW4C@IV{~r!u^LIJ=TyE?L{J{ z-T6Ei<-Q~f{SC&4kc{Vy#7A^PZYQykCrhRUwnTSjSr~FztLoc8M}Ci&WNeL7-0&ds zF@ra^*iom^vgYWhx5ILx_9%OZZ)wVH{#k8r5M#p^k6p5mD!=gBnnGQwi}#B)CnB@n;DHEi>?^!uASSCI6j zHfc`OS8Z6up0|3TjlI49WEWj`Gngh5vFYg9ic@)PCYX_b-a9jWH;lQ8I~KEE7gegO zlJwhCur-|j?YQvfAg(Yw6fw`f@OE7RU{XS~-&Xd|&3QTgbcIQ6_@73)Tfaf)c}=NR zoE1?2okcO@Dwf1-rxKcrh-oxuq{6pOw4+|3ZKo9)4ZV&26Bq6DInn60mbO#5!0T+0 z?QnHVn0}Tv>5JZQ?enMvRAbgh?o|Ii8mPBtsK{rC^MGOXyR9tK;0uf{#H4Fh{P`Ho z^gv3py#?s0;ok3wf~YBUC+;qL6ujD}M>-ZLnX@{LJ@rvBv?!#hXE!q>=Fu{eewRNb z8Vj)lFX6=Cmh*G#Q@_dDG3l1Pv&J6(`_ZYg0CZA*)>O)5E*lrO#><9D`VNGsClHZI zb#DQd!hG4}h~o~4QqNw<7TlRQPFnxoRk%Zu5Mia?lA;3VI}F4j&y50Aj1TIf@u;ok zFqx4~qiBtC)x|c^q>SnZc)v>ZF^N;9369$%c_?3$ZTRSY-hx<gYD`>F*Eg-6{qtEe-LG0^h^zuqZ5w9z^#qA_J{dj+C*n zTHfm7s#0JjjFj(894xlEl{i65Syp^3>6T1} zNf`0KhIDky964Ph^DNy@b2XipIRo}*i%b=HI0@upt6Wv}Mip zv)RU;>&YBM=;!zmqpZt3U8`%;QpU#H$Ex;vUoKos!*lnH=)SRCe=k3+=okz;|9Saz z^5L8}mqhuDku$0PBDm@xJP;#9<(Cm#YEwU>4k;; z74>cUL&4KiW;Ao}*E#ii-feQ{o;;>niHMH@Nt)9lB1BN_8h)ND&via+!E$NaiBvrl zyY7R>ZZ*kwv<)riJ58^t0$80|bdA;^ zSvNt05A=o$F2zG(c^f{rk*VJB&|p>WL6d-+VW72TE1E^TVGZzSW-Zt(ZSvxcU#$05 z;3KbX{@Jo)w|qx5S(v#LuKu*dA9 zNC?V*n7v_hck$l$a@1WYhJpO(N5n1Wr^bus-E*hr_Ot1G1bNA;-{Kgm^_=Z=x~+x^ zTs!`hm)=!qC;Nf|sSFz&D+7Gs5O_?%aQu|8m`BvL*!**gPcnw#y)v@Kh(>DHq8TiJ zq&?=%<}v`lP#QSiJbymmUnE)7_wi#W=e5ywEDk&&ZXRY>&;rGNTyvS?h)r;Oj04!f zW-md&n}yn9-GDfSkBH4-(yqPpx1OI%_kHHOH_WnIZxtb4%2{3RH9&&jpUfq(X={W& z1%);rd--ncQD5g1=8%%CbqWlMvhi<}|2idTHdiJ&mHqApB04d87ZAlfi;z767aeQ$ zjm6MYhbm8b7ryNh5@kl)N}Hz4esWGDVALLVGDdx5nyOR9Lo?Pgy9wA!Ae8StS~PSq z9y}fSyUAs~)=j_4T-4imUX=}sr-eW5KMKWAbVdeB^Q;nx+RaLV zZ((@0)c4I#u6UO;t|{IY=zm8%G$gsDTdp70T;jHt*4;+QY8#nvNN!~g92o)oVJV{Xr;#(kyf(Cj zcxt3Fcm$&&-34@y%L zCl-6tdBniB_n}nQ19)i0)hs8H?hy-aK9n=hZJI`lbhR$2OB5)iVXrBAuXE&5GF!9| zrtnc8fcF4utD8azQNJfGqPrD=j2JL}`Xdm8ks2lWVOmranBG!`gz^T)w}*_`oQRU4&1= z)$}1_+^t9Q5z-A*_mo&cF!h@dah34{n*~G`ct&%JEV_= zFqk?q9uq7ShQl3M7yRt1B}-1{`aKjNAn2q9dv^UrVx&mh(J*qu=LzgO^((;rOVG(y;u87|2AEEii(S%Hbf@6V8|ZDJBGKFy=R}#bqdJ! z6jCED`(1oNGRQguz1p>I`V|E4zg%1i2o3_vUi{KF6j9x>5u=A+!gO{elGB6A3z@!S|vJ zA5<_(W5F?xa74)JUO!NG$GP>My?DC5kcCHyFV(P_*b9pyFZiJm-OY5I{$0G8o{nT$ z`lV~A_S~W<6*yWo|9jI{ADq#F#31m74E|CS0*C=AlNlCw(A4^=U>>?7_jWntl68m83Xay7!hkK{Y+xJEBM{bkv_?xP+`;RGoEftP3$@ zzZ-V(vqkd#XU2ahTI zfIk6YZ_ovSBnxUH6?q{@GV&#QG`n=lp9{SX%mR)axei1nq?P@2T(Q$%LM#wI$F--m zq#sE=z5nxH0B;(MtKVIike5ANZD@+?95}RJ{!M^$T7$M}w|MZlDw7p9NP^)-POu4z zMIo?uL{ds-Vju&XW=Nkzo2x8)qQ4hbRoKkdKMBn3ig=wV4Y@~c*9TkQP8i**y%PY_ zb^R}z&apkRMGL|c+qNc|*tYGNbZpzUolI=owmq?J+xG4A;r@hvx_hs+s@|%NcUc2# zRR)7lP=s!Yuai~XtKU_W`q+kK3YJ<|%{7tbG$+Nj;ZE4MgBPofnjd&UYbinNe$3^h z!(u*Hh(8v{|Fg9RN@bI+g(-}yRDYC{H(}&qlT8{RJxpfhM6(pn?*$3(jo0kQH|eP; z%&VkvqDGWpDXZC-*i@uoeK^}tc{UbRf%R>0s-|Yg6AB9WRgP4F)@Cr5?!;ITNisM% zna*r*dkDOnXVlSapJ;sMlaO|wJodtWwO=&O%MWm7`e4>u&`O8#)0Vo>bSI5dV%_gr zC76JhYGWxfTQsdrTZ}r(=4J(NJ5j72*+JBFdzA7UeEN?p;rtdZbc(`dJ4>M?WoSUX z8#$t6tQvQZi?#lcd$g^Wxg0V|GExJ$z=#jKWwa&BqvC$?B!HHqsaMb}MsJaq$hwS9 zjxju{s7KPY$jO>vTMG}fqNrHpDdg~fwyJ(=blim@X3>&ipA%uk0H10`@{vY1Fgml9hbzbI3&BIrUSW&az(MS7{y^o534^r_5S#q``C1%v2;j09Y-;kb+;km-LFD5JDYtR^5x+LY^k*KZsliXw3 zRQY*3LZkq3ToalO0<2XQY4pa-h74JLs3kM$m~k4@Antp4!uamiI^ILP3q~8OX!ROiE9*A=*O&}1E}c-+kipB+i6y_+7Iu{9ZI~%JRr~lKFKRc zDaK8_o>oo252RmdxCFnbO{Fc`KpdWclOTE1Ws&}pzhloI)dBJDTpu8OHQ0kl(}pQ~ zPpM}uTtAME^qbS8ciqWfF#0UpWhUpgk8zj=&#__Eqw{&Vhr@XbZ*@`RI3%Eq5Z38> zi*KZrIrg30nJvM7M3$mwTgZn`imyB3BVF`U8elH`r~0kR%7v7U$A5=EgY?x^G)*xVU)iCP7dR zrrmWy=4-TTF1>Fwy+-F&d&_LKE)3FlU?C(Q~rYvZ(x z7rh)OBI906xow_xIb$o)aF5(pJJ8t`+@I2w$1HZ7O#t{B_ehTDjOBH&v?Iu8>MC8l z;Ce7v9Z%#YNM=A=Mh}o5-bz*Fp?HVzS$i=O%8Ul>j3j;eYiU`=_)|auur$GR&pI%n z$}h?*R$G(q^eH-R^>Ed7o%OGY#j0=gB*c)_Z}Iv6NY>S`#A=6_O~wnut_y?shlgJ- ze(%^6BW=p@s`fEDP?4yM6*{SRu#VplQqCv=hmvK4Bo=zuRpV_tP_|lR$`%TQeBAev z)LnDoI8fCEh&>7SwH^WEC|P3|UFm1p0aeBrVl?lLTq`yap^4TBy7w$4?DTn5c&#Fe zf<9BdFNObU8_F)hh0zUKf8rb1_3C9bE9>iJn9Yz`H{)!xFNaf&a{=9*v(d&(DTijx zY6(nb7sC2-#;VENCOLE{ZAZ4%!K)o@>b`t<*DZFA2=wOwSTwC7yWmkZpR1yNUG}Pr zhWy0t6pHyjt$n7u7pohzQ3sfxD(p)mB55Z}k85qAgdUQ?4iI?!UT*$3P^TOm%r>7y zrwe2qg;A0&QaG~>St#ZzJ@S-N7JZF)5DRHVbm_CXP8b*Qe=eL?ww%ra-Ex9dY<<8i zhjGOY<4S~Ds?KjrZXU?&7B1ELh5Ap4v1f?vWX6M+4QwmBMe(c+86m6h@!KiH8({F5 zEuFx}M$WD~?_;0=oIp*iVAFM*Z)Czy-ZX^O5PhOw1mv>fFdll-3hP`G)@xJ!-*OZ- zlkjmnzmfQ(hU84JHl=>gj7?B9agfli_$h7cI`IUCoe)`4PP=k>w!wCQ`2QSvZ9$M2agy}S& z6r**McFp}b%Mt%;8iA0P^V7)>G*F$BGI#y#XHP4==Ex6NEx&{2?*>Ct`UEpq#;uMB z_8YFEIMRHk@DQS!EOMCa4Ir6(X_Fs>oFB!ySvf(-|>SeOi zpq|O~rNmjBUeqxAR(sQXw1R}d;0*1q9WGb(>Tjjz;jasCN{9-&g}ib$vnEdwRA&;&~M) z0Hx^V7c%IQ`ic_G9D)Ttqbzw|tkiXxAE(n{Rl4bwj5AV^?Bt1@Zi}FBNl$AK#No+; z!IpN5VJ$k_R=&i)%xmblL?6Dw0`{!qfBLTIhaNtX96Ef|6!Eh5Vj2G(wMf{cm|i`? z9Z7+?9EUYyHL|_jGwW>i=5?} zFuX9k-7AN+#a>Z|uejdh5UR~zK*G7=T{6Ylhbh7VUTAk%6g}HmRy}0Uq3pNO0d9h_ z8dxoE@(v)ZFK!dd`uy9g+Q zg$>IYEtj+KQyP)3(oF9H-u=%&xfss?@)5#n!aXtzCN_rqjFO6o9lP!Y_KcPcKeO$s z{hVIZTtk6D2u8|m@4G6-dQbL4(#u@#mWo2IdH*#ADE4x%4po^w{ufPY&MuB>!2gy3 z8xUy#%GIkzod?Yatp{OdlP+bcizBHRj|*qo$yIiU;PvTiR)z-+O09X{{M_LC7}i7_4WIES$KI z-8I#}f$EKxzh$ze{U(5hyQcuhy7v+)$ugw%l>av$8~~HzkRKW{!6EcrTKmp%Hvr9x zJZSt|T z8Apbh0lMM}Aj_EQa$)qq=z}}Z5;vtYU7$bw*t2_?;BAgvn%jb^1(vO3zLrAKKi>=> z{gSP^v^TepP-cI@w(A@r^pq{kA?#ezu-x=Lh?6Lv#=xzaa;phM1 z%L~~g{wuC@d7d>qoPG*h;n!Hya=nm8r zXf0M$zQvy@z=l;*Ux=7uZn}i}Usv-r0Ox|4e_AUpxLY~48L`F~dcCaZv;nOFy8-uv zL#ri&K6o5r+$pkCnX&j_AElO^hbph3Mp4vQDxw?IJS4heRz$I~C}$;3oi4xYFplX- zlq5)z5xn)Dr;!NSY5pbRS^#-p4TxW&yQKmz@a9v*Ko3*Zv}S7>KgrnsIEtBwi1p8r z94$?d;UPrQUDw%#EIGDde3NcZ{OkR^`E2}h0p+E4+tCl#!}vr+t)CxEMchs+X3F0# zTqC01w4E5<(9d<8iKgu|E5@}sN7YU71)y#z&+Epbz?T&d%T~u>aJQ+~oQ~gqlg~u_ zIM*=p zmvV)en^BXfAi@Q+IL|gxjeJiuq!xuQ{-nx<9V_6v^7DjR(g-*coX|>YK|;8N)PjoC zq!Vw2k*HcY@o3HyjvD^(#yu+H1>kiH*AzsZDI8*fshTbTt$N^YTM&cCZ1iusExn$G zpv~^IC}qjaL?}x$aKS+wzQ1y3Fg1!RJa46;zfda-BwR|WO)+(Dv5JiQU-k9Be`83Q z8z4eV7{TL3bnT3W+zx@}ny_lx6+RF4vnu^|3H_>IT{|VU&01co z#wm0#0w3gF*G4b5=m1NL+IY*z3MD>sVyabR8lre@=bJ)ri5Pr$R$vDBKNm5}?5-Qn;;SYQ~?b#~F3)I3?++t+DMQ#$rTOK4#*YMA-Pj_4b*GNG5%7ra9g&k%>r#!nmywRN*A2 z>hO}wC%1VuCqOSh$|+$97)9MpMSJBY8!lAAvS&JhH7?oPFn_P3%d7 zRAHKt@3x;;uVdg^75T$>?OD2Qpl^`7>4LgyCMH~}nYXU*R0QknUe;Gc~I=J+Hg&D+8*=g9dhkJ|kG~cdE{t$vYY~i^b|Im?1G{;r!`x{VcR9hM_IEre;J@B!Lgc(Uyr?Hqteq z1M?qdv0)82=cfjM4aZIZkHU_DoM4t?DoH*h%(W&DvQ|UkfH_nbURDX6uIu!{Z_E}V zu*5Lp22>C$i*M{YZ~Q0xC4lbP+XTQv9Pm>xHJhLwAgtZj-9${9bs*h%v3(7g&AZma zlH#NcpUMu(+_`Y}33M>0ITAj}zU&&k(2J?j)*P~}k`E041e}fxrMmAOFdHuL91twO zfO<*Q;?LyU8r1ZVWlY6hqfRX@&-PTNAIT|tGLP<3H z{ki?UDEazV=@-4}Lf_Div2~HiJO5B>Y9Kjyxw zIiSrhx-5h&B%@akD|h!HBLh7J2}QvQoUzcf6>aRl(ywmd{OmE;7G|W@qux)e#~_d- zhBv-{&$(R%rPBO*H0>2TshOK$nXw3;;XmyycZWfoRlBMC8^{!y+KNV560|jf7wIAm z-yhFg^0M1v*eHpt|KP<}n8*#wd`VA@^M5|f&r1EuD$F>@yvwsKuVge^H5?kB?I?r-eKfIT!jY$PvULt|rugU$fcoiDp4jd7W!+J7g<N^g;|1ePy zFJmWQ$72U4LRNPykeFZv#|E@9*s%l>nzuPuR5i~{O2;srjGv|-{v{<5Cxa{~(yEqq znz#g7!OrGp3}vYs|50Zqdx;pk#UGQ<3ai8D6oGIy4*SEv;+ap`)QrPBM=eGZQt;wg)9}i6@`~glSW5}&5d0r zfE|H}T(rJ|=R?BtUA>X0HKkeS5*XrP((S^M{R?I-=<*hjP!~TQKda1d1$vY1c@^#h z^m^O0AoJavvY2S7y|~FeH1qv%)o3)Bai^=kKDi9qX*-U?mZ{r@MVL`n)F>$x#;WVH@{z(mNRm)W zxg8B4dKf7aYBiBL3gpA4a+v3hGY@dhc2oy*I#i{r^l-*h4F*R*{YFw*y|ajegm4pc zbNz<+&z$f``t|k;{~B{y%l}WH{fpmm4$DiyHjpSuef~ zkyb9zK*?tzBJQg9n9vPnFc!P*FF%qt&rY1MVM+uhP-35(J}ARPEa$D%49(06Q*hr2 zRUjc*{xXGt%Tz(gM8iZ+Fcre-3iMYaY}*c#QyqV5$VaCQIk`r_5n&q_xT#Lif&&Wu zPN*G$Fl0F*EYKQL^!*OD<*C@^fUf9TSzBu1eEb!GC(EYvLu{Z)pvpqe`f)2S>}~@s zSmYQ4&r21owV-bQ!1nf0i*DEsbY#8`4~F5s0u!I-2hw82OXN$`OLVttQ}r;(+YSD; zJLFJ|0#1(u!>+AA@W{!&C%Eb$05XT_6QX(_+uvUtCW`~3dsloOgJ5sZv7yT@vAb_f zzuRu@n=FTsmaouzR67{9^90a6Q(N$RN|WTaOc#APeCk|cUy|3X_na->+gp|7=n3bl zRC$A{lov|gw=%mo2f+J5$32onLc*1z$u}_d0pnjo;16nR)7%Fv`0H2|ngm6wtXwV# zpor8>1@>p;V9o?vcru$=QlVS($0ayFSq#9_oA|*~3ufh)$)`a08I_a}>hHBJSw<7N zU;M7Y@vC+u&#micK^jW2lT;q;Xo%BS^#FQcHq~~GJ3Gk*5YB66?ik`-B@Lt+)Q4n( zvp`AX0w914eZ{z@ZkmNrSB_dY!flW|HYrY7ZNh5wXtne)kyA8h7E0g}hRrHUThS4C zE-Yy(-F+Kv+X{BsuT8_i4PKIj9l1`q2dD2h`|I4riOK2+=8$2{x@FVAW*~y|rVZpF zG}Kk8W44!gyc4VBYhiW~<*q^y1oFrRGofpLy4RH+mC}3U#TWN?1p#{))RlqTzkArm z7b-mrz8NIvjD1L9sdY^(kpd6&9D6T`k5ETmJelE>%v?m4aqR!Tz?va+Ms-7G9UeR* z=Roq%_9ihN4Md!67FD!GZ+<$D9E@l%$AbKk0_}X3g6IOeg1o4+6uWUpNzd>7cb!T< z;@iGoCZ~Dy8VQea+OLAIo3rv4v8GC(Ww%P76QxH33I$(LScw8?=_t~%c$Sh&GDqXO zoa`f|bkG0}+q$InTmNi6&+3?My}k3UKQ3%(7hV&Equ3z1ZRj(-LK^waDi@)AVM3c! zt?aq~?yZKiJAM?bbGh8QP-zwE+q+#j?FXSEJPYA&Nf6?BvpFMtYss}XW7Yi;U!_G` zzK_owQPTx;v*C?!c2fvRBR@9zUWhYho5AW#jZZ|f&S-zxi`-O4+SDTVCmz~06?j)K zB{@V%ez?qz1p>N0NDU7e9)`2m(}hx^V_~D8 z-@3k^&Y_Xberc60OaWl!H>SoquA9!K} zdVNyV9A*^1+RwwGJ?w;8ocRCfM!w2wN0JmRhw$G%L6NkNr*^DbG>58}`r=5ls>_(? zirE0dgNQEz>&h=+EP~}0lx`g9Fnv(z#nNt>6mn~}HS_~h0CvXLEIsDhta3jUX`QV> zR&u%?6y3jG-=hW>%!fK~s@FS!B}v_Eq(&*X9P`+PID3qGi>!iX|2~sMD~qmIBoS-5 zm0Kgs)+PD&vb3STCe;>zm?2At)pzns)j^zDr(Q+%g3$NlR*9&3Jcq?tgOyHzBRy_h zqD5?7JXEZL#c=W&pvW6PHT;td*rTX%ioNf%i3wq8HA5l;ySQXz{rSS zPj>VYp@ggWzXCJ&iULuMoDxfbWN<*Se;k^scx&jsvgs~J~A$JZF^p6{Pw=r{ye&nnL={cQF zzMa=x&lo4WtXbWhX8~9nL15kU`9xKJI41?hf;8Og+`YeI80$<#Isx9%*P2?q161nhTxBpr z*ES{Krg^SA!!it%L#RH$m(hQZ73)?_10N=;K+y=RO`UR9e0=8nefygmLmS(5Fj@@} zs#*V00@+N@h*!`4R3u9S>N2(M7P1XcQe?wU3%4C(#zu2WOCQsMA_Epl|6fdgaKEEo zr4cR=>$S~M2DtXr;wZk3YN#gxOfw)4{RMb3cRJ8^?MJr~!3SukFiRTjZO=cd_z%iE z-}>D-0N3v{WYu=dqUnS@8~-x&Htq{_e;18pA=levv&#PF7mijbi-|JNA9PX? z<6g+v#~HAtVgzl?n~3EsEl8+}W=O1e+^_%BfVCQv$X8ns|Bj)!e?0T$QRa=FyVZRr z2k%bKMx;6}iJzNUqU-)Z$wIEey6jD^4rjZtdhseBP*MF_L;MjflwRRyTXj(iLN7a<8o&g*>`&?ETy8-V>FlseuBF0d^E4!|&q?Ns6MU+^G^P=4 z><9}@aIVe0`qNIuRFm+RdEBBVk#G~8Y_rWp8{}|T6zaC!0K#VI=a})&;-6$DmQhmh zvfFRSWIOECEr-%XD9oIOUQ-4AIZSrmRG&a#*y*nU?O{|=+af?y>{q6pum_{I=Wq2P zDGah*%VsU^#Vgapg_Pab(&TlI;v{k$f}?BM0?P#Q2_rWh)L-M%<#pRUS_8Ubu-F2B zEqS+Jx?egV&(t#xUl$d>LwZI@cFR%9O#XQE6*%cj+d?vQ?P|h0URyn19O{G9_$J95 zGOgoWu8z&+(!Ugkj}`l0N!&sZJ;*vD)m_l8!wR`c7XHeF)CYsb00(Ad1Fd?^sbtQf?!R>wY zuTLi2t8t?gC`1g!b8Gl)RRFX=v{+C@R1#D8%!`1jG(r2|rC3|jy$Hp6Gina(_n94B zAQMILY@&wX94xNERVu7bzCqhf$js%t$)#lVu8zpiGV}Rrw9pZ4cff{9Xq-{%=S6;( zJ7e#a*(Vp|U+Tay3u7q_ap%PGv7eb+0~Q2!lwf?5Kc%2YOSRi9*X(#Y=z|nIaSDPf z3?buU!q0Eox*4}pm7r2oN>VEBp`m*v->I8AD`Fwej;McA;>!FwuW^d<*R>zQu*oUw zk1jLPf2{OxE2@db`Z`nXaR}T6GTy^fg6&08@G1=t(ysa(G*ykt8sM|d4bf*ZEYU7X zAWloD%`YMdphDoP>+uLCyLo<#XGC!d@ouk;1>CLEURn6EV=u#oR^H5po@uQwoI$}#YRIoO+^M8u=NP4QL**{o z^JUQP*NGhX&-Rb~>@+Ge`lqtmZbyEqYQ=Fj-CW;2VYEE|yqBb^)26JATqSa&%71As ziZ?4<3|B!c?_w?-bUfGs_Pyx!Gl3Pw^*_!j6EB0;61J+4iXEa!Z&$so%+}J**ymw= z`)f&E?(Nop%B<^g$#MGt^(+1scSk0f|8@}VD)SI|%3vb>A+4Z0F_!+L(Rd8NFU^EE zOs7_FK-J~982505Ydxy`tVyFm!JTobCNRl|7M;re8_Z9Vhp=%x;5wNLpN+)iaVGP@ znJNbUqXNrX91Ud{ta7{UM%tHPdyZP4#Wyvz6+1^=D6G#smqs;kqo!8ZaOhw{t#gUd zu&9anHu`1w;cVr66JmJDc`T8Njgfm2(R&n-g=>>#_ri>}pSlOMy*3P^*_KX(!RTuT z`C@722YXe9X~9%8A^K!)F&9c@`<2`%ZP8*LMl!o;BU3733kAb(fUT64=cH0aZ(m{w zm7{r1W|%y4dMSP41zGA3%tt?me?o|gkBtBYt*dIGg^pBh$7m+R%WK}l#NQk0P}U{o zi6K;&YY|1U1Qz4T@RyAF1l)%rZqmfB+|{W%j`o-EoVyIckyVg3c*%CP%+9R*+!d&CBLHIwK>37!(xdUYZ}j8BHA8u?>J~Tb1u_ zFxC+>Ow!?Nz~pcfr~2|-O3oZc=dIZ*n=!oWuh1V$t%7$dFOkQ~jGRQ$8QLH?&8XMo zip7VVEV&)u(*dzw{=$zSUVAE#TG#s=U=>f5tqpa`;t#Zzhr4y!QAuLeWBX?T*O*RX zx99O?k#E4-2Uo||txh~$5mom`YskK%(iSymkjfi?_CJ%`W{*M7Jm&uOlLWEOn72h} zRZV++nNfb$L^>QL~L}q52yH!J>S7F6sl~B^9&J@vFrGf>t)6#l$yWhN(E&A2+=4J@r7jd zaOrKx{3vl5&E$SWQ9TB+fvjj3u5h6Z){SpaM*;X7shxzP;GkDET>3nduQuGH$m%A1 zwMph|>DlO;{!J!_ZspBj#LcXtYFwLb6twfHXKYBO*7YCOZgf0GL;i;*lrxy`W(2x>Om8n)+T;On%xCVeT(P(JS#-;CX~QAm>Cc6$rqN$jtK5T$nu4O zqJ@ETUIB^Ejq!%7K#sRa3ObYsKh-X!tc*p$%|#g1Tkb(QDqvQAv+{(TX1LJ1(1b!Z zbqAmjMU~-8zN|L{{n&++c661~)Cz5er666Gh)poJB<*js70#s@@oUZ*RcXb zZkFA!lFoQj*3PX`80gb*Wh41G<4;{&L3N}HmIwAc8y9oUp8^7Fkq#aWCJ2!#7`z_C_GC$t5t7+mV4Nb7w&k9P3hQss9 z4LBujZ)V#06~jonW;?Xv{H$oG^n^e?W8N+VCXwYW8VZ$Y*f0dQq9kk3{dcCojZ1Zk zcze$!C$lAZA2KIwNCPVf}yR z12FS=YRjmb{vJ4)u1#z$7m?S}W9da>v#9m{2X<4tc9>=&qW7|fB5^wcq%F;Q0z+=1 z8qaLESQa#wX`ol}foT3HO6jcs-EIa#375G!cmoLk8JSZJvs_KjF>Q zoB`Gsa3q#Tn+KmI4Kj-P=-K8BZ_{BqSyv|FEh!4x> z2J!PzSdW~+Xu%$%CbN=p<2$G8-Y=zr66?9eQyLe!JxCF?JOUb;5>`?$J-;*;t92O; z`xG<+LFhUsMyrcj1tKv9LP)4xIRG?b4je-iTF3(dnfNfX;oUcT^?K7v{wXu_;K9h$ zx8XkNwYZbzzS-S$nDY*F%7fCYwdlnY`q#c|Uo3ds0ZPOnp!w3Au0Bw~5{S@csvok) zXHV+zzhL~66x&7I`QYo7!BR}NeA()h(;dnkYowG3;yr6>pjRTgw=f2nFA#mdNaUC= zy?p`azq{v}fy|l#Ul3BTDBJg5tDIRR;+g@Xov+vPrfUxxT(G8j!k$Jr*oCLK@P$8zNi;Iv(DZE*M5rrOPI(VX``PvC# zBVClBRAdw)u$}tfxi3h+uR(&^AU>60drM(SZE!f+FU(BpL7V0%mwxYu0z|n%T_o$_ zQ`!igzw&mxf?c$}RB)G>vl@#bV z9=ogkUtb0Kd!2-iK+@j_dyoT%#E8r#^_~| zTkZeaXud&r_lu+wDA*Lzn8m>9h31^a{!rU`J`)j?1v1;nt;&O|`zlTb@%##_By$fl zjXQ>T4jeux0pIwIckA;L+#RnOdo$#z6{YX60(le4ozNOziq=J+xYPr~Q7^I<|00B5 z)^V98%aSRTt>SqVG)Eg)k0TXmBnq+qe&u)vGX=d{$b)6Pi2e`48J4(Rg3$e^9Eqm* zI6nWLYs!(Lucf(~%!byWjipcz^Zr!T16 z&Sr>$u-e2xF_ zZjm)trX!@IFP;N?7=3)$6?Nn?x}G4(K9TEygK1D-mb`Vi8@{7FL4NqE9dX(_7$CBk2 zHcLj2RpYF;sID zMf#6h&&Lf~sdAc^Yb9Fqr!GxXAt%W~(B=lWZTK@WnReaGpkYTUNzS;NDV5y++GCdv zHKEd8D-%ed<5tESwk81h9acv%fA4H=7Q%^e%+}O){W@r${JN3doo2!^{Gfdz&_7(* z#27nC%Sqw9U@z9UX_-|u8uY_eYX{2Y?wm|<+b0&Bg)JE0@p?Bt1LQmT2|mH+kky@N zf*4=Mpy9=^`%?bvf3p%C6<5-l|2MT)CxOwfMPe0MkOZY3&os*TXPTM68G6EE??G>f4o6pY27fc z`7`|&F)}X=pDk(!(7#^@ym_R(T_gDOruZ+wD$!Ei`1eNs2kfAMDxnrG0Kz8H9%pPC z!Wh*O!})+W!g!@MrBVs$_64~AX4C^eR9t67JZ#MurDqOE9tRAa8S*=SCkyfJLMAOd z2sHRtFBp>1fB8h|qY{BnbI3Z`$XC)$h~VTQFO;(iC5$sU0k21~{d^FANbtcp|7T5P zRB^JOW?+1KiNEE!kiA^NP5#jB*iA|m>Y@k`tAb-i=&HC#jgLirGVj;G(49fyOly+) zeS{5+$&+uQ77IeihW+6WhFkmX$w2pwwf}+^)UrG6_e3D=hH3aB+`oLweVW*+nqr2T z7Z6&kDx*|fgW|IUO?(=&_*Wu(!i1wMEm`OvXSCt!#T_HFOo26x$uwixEu~aLXR^qg ziN-Vqc$}5Tl1_SD5^SpP1^AS1v{UTQ&TZZsSP~|*xiaHJCm{2-2SZYrS z^XWY}S|LZ?>o3%Xg^8GeLS%c^t(~}N2{CETfz?3$Bs?2&r!cMq-*i<=w6ggO^H@;w z#XhuvYEt$DMT{1|=V_Db{OE2rStiS4rCgUKP^TE(<6sI8t#qROHUo_A+U~L)f(1d;3KJ846w!wx< z?eO-G#we^zT1;-U%j? zRMu$XuaQ625jtqpLe_`NJV57^mFbhJo_r*fK>OkdPm!pF*Mq2?O&E=@NX<&>5&#Mm zLd)WT5g0sHvP1Rw1^sB8%v~bR;rVBM*k53?W?urnr~7A{$;}LAwg=%}yzK3Ii`E$4EBO0$u+M96 z{?WG`#EZ5(ZFycVP&~v1PjSyVs{V3CkM_JTKla*Q+fNy?sma3B>i`X3h_+>1_-&F1b;0pNjq`&X&ZGOWY0)IXfpV-P9Z7 zAN^!I_~v7s`pr7Rz2gU_u`+B6&ca5<*kQ2-jPH#YdU|k!gNZ$e6PN)a66ubxsvC7AF7m!321;Gf-spdTyn z8|Aoner@60&`hJ!k*S%Zv!kNpvP8fMkX@;vr&??h6c+=e&smvh91yQ_z_L~GFnZKT z?9R878a{(l==CTIh!9^2WwsZeZrzCVFFlcHMHS^WPYmATkn^FvguH3zn4=yR7J?Fp z-+8@wb@7GI7wizAX-UBg58h0If&s(nnWAtLr{9K~+u5mwW36j_s14_wR~ED*tqCBC zr-OV-aB?#x%#j*0jB`9`h)#!$L9sZD%mii!CHIvyDV!X36J=4~#!~0lq%O$S5^k8& zh`&Bat{EttZnI52Imt-6Isg9HEzly2w)0~ZVYPeZ{UgT7v5<;N*iFlQ$YrK<~ zdSs;^R7?`9WE_l?^sDUjnd(dM6QTJBsg@}6IEj~50H(QmFjC=)Neipt%`Z-9Vo32t zC_68D=DOJ|%%KA+Cbu>Bc8P{_QBKCBNNL?LnfvDaeQd=3-k_z;0Dv=fg) ztjZuP<*{`;&xX*Xst&%~mZCP~d8jO`g!HxW6l%YSd9D95CtGXWFmBV-;dz&&jIvX$ zvuFh#>_&ZV((iP9&c%TFdeSIwO56>?j za}CGXB^HE(C2kx0^eG9RY1oHk*f;%t?}IQG+XQ{99wauGJ>VJDHBUhcD=9TP(D2v> z;C$uH3;0(9qZ_olo^?Z0FAPltO&q)zdEx3{+zfVvYk7-41S8d&bj+HZ_(A8HW}ZNC zB+i|R<*wO`s{5)_5Yh2ka6_T~1)SR!NPN@yV|X{-3RKttfI(lASeuXZ&~7Htd;i z>8mh2uyjk;Gz16`QOBGbKRXTsrYNxXRq-s&x_U}+QJtFK;=laDClj-U1k19ATM{aw zN6^P0goX-YxXVS}H7*`vB=EkGO_a`OxE8)j2_pEJkTMs3B-Auum6zrme97g`u~Zy@NEMwdgy(+25_=7yVuIXtJ(86PyYg*u!nCoJ%7fasShyTcNI(!FOn~n(8fy&OCI)l6pk)R<^Nt;+-9v0RCt@kY8@TXg8~Qgd5?_3A>6Y z{Oy5BhQoD5^Q;4Do>|j|o%Y&#N7^K!jxpYOn!bACzOeR42RdcBGO8uMq0(HtZd*0u zBijY=x)dKwK2TmP80Tu3pA9TTs!S<$G?1$C5Y6L)8@)O?$55nyK9ZA&b_oS%)Tqq= zC_x4c>#x5yv6$9-x#%|&*xF8f1@zWSFKKwi9LuTeyV{K}&ID&jir`Z4B|#R}qbzaj z)8+1MN^5oa42r#W%UFYanv3O9g4QG{9(f0+A(_&D6iWw!HQT)T_onKT^jW%)3#%Ws zx7npC@>uE)h4UY;lQt+5G}@zq?y7QHUD2O>4IHopXg5!o*5&Cxb@++thBo_uZYdH9 zsdb1f*mPVb*Wb0#_d6wQFQ3ledSus@+#bD}k=T+VO^(EH9i=`reTK7*jouAF-4%Ic zb(&oSU^E~qnA)0Tl(g`HxlS7tfuDcDsBPXoQuiiJ6@+^sza7 z{ge8?xYXLw0_*t@3t85+q>O$MrPe<~1x$b2kmeG2P%H9?!J(+5(TNW>vVBjuz4l~4 zw)5iFDc9p_1{;HBK@yEpwFv|d)&~?VT_i5gS$cDaNUG#|Q+rC|oPcSnkrh#dI~BWt22i2jt~hvsPqSb~T+4fQW_iY)c=dQ`(%mPOvQrpI@9J-ggO#5-j>yYkUf zsIBKo(rVkQ#{=(W%iQKOiuOuSCOvb*48Zp%$ghpw0X1d${?O|zoVtr-O@^FORKRlL z@?p^#{VHFD`X%wzDpr&R4JUGCnq}$Rl?DktwTnzBiu206S=@p)a@vE}efH zyd0YXNRJ#QSZ5$4txO*7>eP};x4&aHiS9}SGiZ@v)|_3>~w zy^dLiWQvt;y7|k$EkJ}iVE&h8`qC|r{>pO~syD&Vt3?u?^#95(UAz+@2`DUNoJ`rD z=h?Lpk1(9y8Lx%+M@S4=#8q}$bmGK`^B@L4@VO2_E<*rH*YT$YKpR|kZAtMwk()Hr zYX_zR6l+-28kWrmZIX*4e~vf*RQ?osnEyqcyQ+ro?VoyWM8hexiM&Ku-MMS8y*ADI zg{`UmT4#DXzmIFfC{KhbQk}oY9(%0%)IJ*N)P83>>LXnj{5hLvUun1o1Qv4$h@+Zd zKwF_99n3&}048c@@V?b{w!Avlx`IQ{F1NmP&6dt*-MA*0>N5FkifPAIn7w`%oUt18 z(wP81l(VSiPb{PKU^L^_9j%;RNpyfPAr{K{6xSCrzONwfw1dUc6#uDgR>57a_F@f( zNouN55k4w+o?B26Op5G%WfomKW;*z~ogK4QWF};rY#(M$*oe64sWfSyZAspnLX&PJ z1qBdQ#R;}~7>SPs&+JP*{)gvNi9d-@7xcBr?pEofc@FX&4<6ii{q@(M$C{wFP2Ae` z`s=UH#Gi3sYbCiX!^|pe--@L{Qj}eE(M5?)$M<7TyciF(cZw%XbQWQ-UGxQK(cGq9 zg`KAAI^TtpHd+Vkqu08HF`Qm|h^Ov_bR^E)Z_>kl{4*y?!@Nsdc0=}bwp?*!HiNUn zbSfJybI^3lJWy`1T2%yM?^>2hZeS}noEuSkdZZp!M2Fusp@ zlFWPu3*X*GXBvPwvk_wT0;8#<^E6pCykToa{o2~3ja zLajvzA2HBO*LHFCgR&(g@9dL^T^ISfNdN*!QEfl4+87~jBVPX%AAQ2;!`fNbuAF^R zAI!a4H~LV<_R$&bmF!|&HxuxRbYe`1!1vs5YbwbHJ zPJ2}IfnY|GDU5t5zGLT@`H+%v14)@4<_GC?*Emo!(yAuXxM)CN@q$1B1(REi zOE;UrZQHxOE5f?lvpd_|ZEa)x$Z&b;vui(eKCkCY&v(p{mNX&eoRDpZ_ZikYz6C9t zyI`!&$Ba{@n-D%i@OYAE=YtZ!En0OlxZ*QmEUd+k zu02&@NgK~2fTRydm)WlpOy@EPkS6kHgdVLBEcaYs<&hgf1p?9Uh4-0fo|)*Af})2H zUU%Jf^9Ufu4LW37XjLJDeC)BuR@b2}Xq@#I+#_GDIriUw|I}wJZ`7}IzCkN3zk;i- zt+m2(|2lg-o6M=?t1u!ukG0O}^u>LpbC$D5JQj$Nw%o`u+0@rwPMbW*gIKpHZ(ros z(=;m3=xOY}ix-1+3pC;DAyJlDgr_zm7n!rFHWxEA4-833yz>}){Xsm$;+BV098w|2_X!9aD}K^GMs zO2_{oh;%Abod3o4tZx7d7e{uc9H}iJO1nTvn-J_enAK@=kcA7l%OvY$kafWnr&as`kj#yQZ#YvML((xfDS2mm)z2>1p- zgrD&hy4mWpy1%)|Ti=xv_8w5E*SWT;D**)NCYjY~tLg$y33&)fejCWi6W86n#8Y0~ zK9TLNlXjF5OX|7{*S3&g{A^d8A`#T72Vdl=%96d^#4ht{LFjCKNdB zDY3?1mk^LbU_SI&)j94Fyo!Ft>eR%jnkpwQW8f-ZJ0kNw4W&fM6~#1ooZ zKmN?q+1jJWW_QniC3_O1##PukuD_jwt1=c#+3&Ka61FNgSNgs}w^kUI)pACCqTly- z*#=7hNlZVL-CR=-25)KSop)}M`H4oYSMfk5t~n5S!^kFvN&!T`)pPMpF5DEg1EmAG zm|Ine$k-376oB|L=^Y3kn^Bev(a`^a_b1l~9!le_*Z>w?4}tTnjtywUWpaD|g(5~K z&=VIO!Wk9@X8~fQD*JjgJ0%FxTL?p~%r1N!I2=Yt+S)+^!}=z1c1QSl71JjXb(_Lf z_?mJsCE1@Et-0s$BAyR3QOQcrpItR82Hzwm`GBmuhM+R9;DQ>cBO% znd3y|vYRt#uI+4<&+Pw0!PWK~>ru}cCw$Ong;tJeNEQPfqZxjP+rQ+zoQ)9ATN!P3 znCLw9((KK%7=!<0)xmeN2QjuRW^K)tvb%e!G(Px|s9(MI=}j5B`gTwS5GuV&Fj8t8 zbOC_Q1r7wR=}zn&?KGI{D7yPD(i3Tb_&ppiLtC;U0*G251(0gH19YMnO04-!hgUd9>%k+ z>`2Vmlo3AOqWpVM{`)DT0^4kbw$6_F_^6FEm&;+Pa5GOd(n)#SM$t~4J80aypwB(9 zU6d8tB)`{1nA=SjFdcJtYCL^FI7)DgIP8&k)RVf@b-uTqXs;Abm@pyD3^~*0%#jCg zx=BI6G}04+JL}M$Gpf4^YD$;4amvqcBfUsp!G^6RGs`h;+O$mbD-pgzh^AI9 zc2|29>wiqcHumr($7Q7B^MP=dKk6_L`8D!rKmZ7co5b~ra)U2dfToDbvH4@3N#k_R z8dwMMtTpoq*7+H-qD;WvV3-T_DW$nit<`k8cq4uq3)XC z9!#0n%5#&pT0R&0|AXhC%BH=zH>scvUKhbnEhykxNoQs5S65NubN|8#KjAOURI#S> z-sj=OVYY!Y%&tptwka6i+IV^Vg82adWtoAID=t$G6Puf0B-{wA5Rlf@D%z?q>RfP` zbb6{KfZHS2x;pJq`+7)STh^fS3c%JM#dg-#(v+=QYRSG+e2{IfCvw2&cse!<*ZZLh zIbc*Ks8slQ_Gp67>LZrQo_Mi>7UacLJlj5w0m~eIAb>eo0!XSG6<*V+QC%S8bkpfi zgduPY-M&pN(lysyqacy4J4LU1D*_4=FObJrBF#7V1>h5clY!^`ahxHd#zo|=X_JV1 zDMDuz62bx58F{fmp#&fr%+?X=VO1qtp$=h@s&1hZJCOrAgDLiyV$ z$8;Fh4*|PWFUmHL1MwDs%WS5T$FW0|2Sv$L7F@WmtF4FE!atCA?Yr9c@jMFM?z``v zI&~oT^f3Pu!Fy(iYZnl9v|*4qq7zs=obc$*2|N3<%!>%hwL|XC;-Y<>yM?)}v_f+& z{A}Z%x?qe9)o^rn(Pk8m|j4yfAZtvu3KUOX!KCuNS}<9pLIFm|u~z zG^bVoQTSLIT=GA}Js%pN`PKA1?O@1d?4@OiI8p8q#9N8!epsJM72<%h{JTC15{X35 zJ2G3J6POi1S}R3x&CCg861#9nUv-vIr#A3igqNqgI)5r`bb-sjb5>+N;w!#2j^1m1 zxOnEhYZ9g~6MV%LS0vn9`!3=eiigQ-8QMQhlW-kvQ5&ss-3bEHvaX!4mL}3CtF!yk zAf3;olf%i`$E=U#mbtgj#93+bJ>!>x1)cd?c1HF9#)-?b25ST&Lf_3i2COQxAI;fa zY@K~vc3-wyc|TtaSaUHecUIc z9$FHfkF=BF$ZB5giVyqPNsR2&D?*HRLs-~cCKB6x%PCx&9O%?rE!f7~D{M)i>bU|_ zqq7+tt5S#MjG*mh)T{Fc!m=~Gt#3GmLN$A~(WFU}(&NtYeW);~5aK|jE|xFBGkvfa z{MD;-R4d-uhW1GjlL%ILIrHF-Qtl)sqoS#m@uE-F!aCCH9GbrAMO?$$?|mQd{8nNc zyOB5+Er+|yDRj8SwN3%acg}9<6t*3UKC=$B=|%l&pQVrL{B@37Tei;E@ws&qkvqE< z^@?{r4I|%t%=YxNM!W_DRQx2mK;KETAM_;=pk}*up%V%O4{dekp7nL}oqJIC!#WSe z6$%c#DyvcL$h5R!EjvM(sOS$*i0XC|o`# zx$cmKvwR_ZNS}PTP;?+*?u-O*5m#2SBcaaDxDmhJJIza-dER^Py)(?gJ4dO#m;RQ? zvp^pZhI0vEZLDE>4UDW$mvHP&wWMWTxxnSdiFlpEuD%_r&+Tv4!EvjwEX~p~MCDy+ znK9YKw`OO*C``+?$R1+zhmG@@3i!u5;IC~E<|=wrWjE1A{NX6j8R<|^KWZD%l0Acr&VTKKD%*l+nKi;k`o?k_pU(A? z2MpqfTFJ|>pIlkZ0Qo(1$RURu#zOi@Xv+7a6K!iJLJ&!3lu+LFDcAn_EY5<8GKPh8 zpCQt!97d=4mWN<1=$k6zX^^TA=$HQ9ld6v4Z6sbs!8 zPf{^vRVr|HQ+nqn5}k2t^v4M2Nranx{1#jo4b5@Uw!1M}j_}f~f8%vti%&aHB76*U z;%Qtz$a;mM>&tQ{agqqWMgprgXBDP%Vb3Or#JUSDpbXVa)|kAScH5P>`ZPHUGy%VP zx~tU3L7C_<@c27*zmt2d72{kOhjG zR?R+iYT5IhN7B90n@%9=j9(9tyCak4BRY`x>jafvUO^ZMk@3tn71254@lE`nX%Mwa zH&WJb5{5HgnMO+?L}9wy_Cbt#g!m$ng$?u0A@WnAc9y2ZR>D_uGV6z_)2@``X3AU; z7R+L7&eZVLXt!IAW&Mf=5pwWVbFJrj0v_jgj3T|4ERTl-N$7O-;Fw<2{8=WqV|7ru zb_BDqG`k@J@2WGq2fphLYF-;9D;&MT73%EGwMF7IVe(Yj#&jxttp@-6J z2uwp&7_eUp(}i`Bm)}L3_{?=3&LS#Mxb}xWlxD7KBX7ComNc#-zo30VyTk{bT|VAqdW$O1 z$oCcFfUhk_SsWpLCtlDV*D!96t@-V|-<7TH|89Kj=@YH;8$IdGVLZR$4FU-NS@vw& zZ3P9;EE}3J~j;Sq3qDOhB%!cS+ zO+OmY5(eEdRKfN580OW*ZceuOgO<>{|vvz8i_En9A4Ey!?z+GsWO`J;$`fqN-_#r*jMIcRFljIBVn$W>#*`e!|FGqoXBj8@EFC zG-sGd>$lr(yTo_EBgX9E+(ZWOYEWlDKqk-FC^{FH>lgruagEStE*y6etc%XoCf#wz z9f>Itc(IqdOq*JGg%j5o#P<=ZamU;wEf@oF$!x1h(g}l#F)54k=r9Lye-NF1oG)>I zq@0s0muann$VSRi%q8_E(tDh8b%2;SXQqD^qDMU-90i3n!pA8?nQ7ytSRDL1Q^!|x ze>GaKok5OY=Kc1p580k)r?9@j)8E_jfjnQoW*4znA%ZVCEyuRF&VcY)_bw2)+9TI1 z=rkR2zw3C{IfbZ0zSzBNTQTGE6!C)K)RwLD=fmc{CkQj*1afj#P63V`$$&~+rjUFUP1yT&e`!h{J5?P*)FAq3Rw=LMZ3Dcgm}1PUsA@;7{rualb}*r zvn4vR^HoNg!i#r*WyBqrO=0fiJ6$royBmDqGHK-YJ6u3HcR>)mI%Rse~(`IfmQ zw#zMoFdpIgv-vxP4+W3?2@jwV*2te*jz=k@NHGGeuah(!7}EQ9#mJ!%%){;4r4p($veG-;BEv;{$M?)28hu|DbE+t&U^`t*yEJ* zOzK0BKp&;DtoGP;koCOAkB!1+(gY>^jRr zo`1=sS&qtR${!5ZK$tDaMLP%+XF(LEfki^TYDg5w6{uzO_uY5jBortlcz{#DQCRCD zt=c@@)S>gn4vqnL*^(jB>`FnhXw!yk-II|3Tf zH^qc2va`4XasZVk+?-dbh3?8Rp*3jU*xIx3k~ zOZP2{WwLDY6oUcl!4g1Xpx(t`o{FveT1**tV1Cl%4m@_gV6vWl_Sx*zQ%|inoIuhW zM+xrlfB$54=ZTa>ZZ*Z&eejh?-JXT9j<}$%D#24U4 z(ny;Tis-^QK%2!jUk$NXgDsWW#pY~Xl0;DI}I?u@LI34y}Zub(>$A~i`uW>rwB{vpZ!y3UlvAs zg@cn%J~=H!SKwCT@AjQ~$AYKH%eaU@7Ua~NdbWoeKFl#vbcGlXfl|=;(wDxJgd7DR z86T06?WiI^t?iauZkaSSmLq0F+-N|i%aiVP(YEPk0RC$G)xu`yw*hCr4fro?I*s^C z90bH!W$;nzIqNf(Jjy_$=4!M+*E37S3!2ZjAmX1K>=vYrPzolO|0{=8!T3 zwJwieR#^a(J%xAE!M8IAOKeo}VbVF2ygwrTV0Okhuky{OQAABOoo%<>Ht3e+mr;&; zXA{J|2fxf-&4v1Q!LE`cBh>8`$KK#2s0Xh=Qh zR(g=i*ctYNRE$2!Hu{kG!Le;WvY#1xIJWDJjy&?nlvfKg^W$Hbp54r{@tX@bWS?PW z$VV89?_(2dcaa;3u@1|{i5pE+Vq|q-IN8PqyRT(;^4;;-{jBzwosGq;Yh)`sYpuLu z_9D1LGeZR)4 zCkcwkFL?xqR1XA^qSp|&tGK?NG}bjE>eLxgrhA06J6J>Vz=R1CHb$r@-*nSW1>qTa z`>k-WZ8U>(+gO=ewe+#Sy3A{5SNpy1)C59IOEB3@5RLnIbZ`Dm*FerAEN%g9QH8It zlZgoD0x=h)YZe!$z&g|CLUp|h{nf7eUd^etr6Lp=9ZmGq(qZ0~-FfGo*|)y+t(2_q zWl|JAB)}eqW}4#ofU8nasv04A<35jDxfrR*Y-$V&KMl`JhEl0*K>Fvxr(3X%LnW za+=sN@Kt6uu)Z3rVRl;Ldd{{bysi}a)gRmZq?A$t={ z^9b<67#Q(czy0lRUu0Hi**BZnsvVMhItP_~fa3_q!0PmNThNpsd1|C{ho!UuWwtNgums|1c~XMM{^lxF`R zyp6i-f)cU>+~y%hLA$iLIN^j7lHj565hhr|yEFZ&gV##XtR~vy&zL>m25xNfFht9N zPxP0t;TjN_2LkpjX{t{88qVh`6ET%f%3Q4;G!I^NPFvRc`dis0<*C^v%x365nyr?- z2Qg$Vwz3|}*mP_?gXr-XGmAfGezCfKi(WXPb?I!**fH7k8EjgC@S&h1zc6v)#H#S2 z5TX*qa#;@jV7S2&K&o|R(EpG@yf+%%kq8oH7DN}Eo~j-EUUzmr6L>OQx?q^%yFfV3 zcz7tAQDCr|Fd_LF-|fi#;XcF#@)H)dt>AU-y>aJrrq4p&<<2_mtUtk!{RZvGo(Lcv zG^ksnEU;l0)bKTFbU=Wb5On^;P94{v#a0+`3f}T}29PrtZoyb!=Mx1HHB5h>uke)z zek-z`Mf@KCpL4h_s~K1OEh8)}EvN9dzQ-MRT-pr49YcJtMyRHoDOKCW!K977*CO|6 z)21cnnFs~7@}zeL??m3_Uunqn^^)@!?JsOa2;sbU{f28fG$~YD=Q#p8Vflj}{2+Pn zo%wUUh)6{N!}f41-Ehv7AY7(*&Uz?#Y`*#CNi(a^;rm+Q!!kv=Y*)R4h(f+w;JOQ) zyJdM|yTX0gPRBQqZzF6#U@?M#cv5=ML7#6!_#eTTe4H8kpRi`?SZ9kJM}Y@pEO_79 z`ONX{%)BmWSI{h(Zm!e3MF zEeM_VmG1e@87T!20XY-;cpl8$4p4K>+D<%*gD8 z8Lwq;Jzp`o@<;@CCJhCpbbP1?AJ%g&$K&0=ItOim!GAvua~|O#+;^beX;!m7VZwwe z<6DbFqlh-5PUco85(!s}$YqqUo9&o5aiUYFEiY91cVj`Qh zojf#KBH--$`c3aQt-PO3IGaUjXTxys0D)M6tyC34ns`@b=7=+ov+VBNVVniH5`st5 z-FM&Z!nxCl`x*jU2b%19$h(?Uj!Y6VKNcHcyfCll(sRmlF-Z1c5cnqK*N#Bg?4fve zSe+&V+7KdarF%QKzANVj0WP?bVOHSqTLheT)+RKA#D)J)^KZKYRmcTwwnp)2n-_#h^Y1L&=NJ@qhT6;#cae!@C?)U z&WH1B$5L1ItfgJXsToHdm>4_z@ff)DB3?CvL$73~!Mnkunk9V}MrN$LGC|YYz!#nh z{wiAi|LH2Pn&;AfnfX;2|Ch~aYkT*Lg+j+lSxeJrvCML@>yFt32x0wgLE970E$2Ni z?WzE%B7D5W(#fM(dJdH%y%FVSGKyGP@b04Xm+{i3t{8ZeO0YJ|YwA$T?+e7r&YD1Ur9tV8;qyiddRF$BZ|j%)i;(0HFoy*~PCeqP5XPBEbj+oC2}SOnK_I zcJn&p+Mqg5oCS%L(b5;xbnyHeFxgN0j?hIRSM%(ngkQpMi}ynjn}|=X(vOIDF4x^U zG*QiH#8zOY<)55peKZ~HEE>Cc!h{L)5v1CLwBxr!=sP>>t)mPj77G^LVG0e;H#vmy zHm1~?o%*GNbzFb_^%b1oIe~fa%LpHp1m}GocfPakvncyl5k7tkL)i?WDyk;-(fO>QT7w1Z z?TmvS&3-%XG;{S#n{bY;d>pkiwwqBpy@sOct85!h&3sJDYBJW9=j7}Ay|_0#CZ}wRPgX^Ge#lP zvtzP9XIHQ+cC}=BH3zHogdlzcO}&li;{7-A;(lS;l{0DUVD|djxV}s8H7DbG6!h`? zM<0FkI^d^mT%ZoVGoOOWFgI6m0}k>}UO4BlCQ8NGCeYSw;>3yRfjAiWwjuV8mla;|6 zSe|s}G~-e!2p^i(jJj@A>l5jFt)kI9YI%(A(j16)L!qN329yE483^>Y2@@uahCyvd zqtWb+RD_+Wh9IpY&N z{{ns+e7IS3lAZbXHXev+tH?Z$U^@0DGj2uevJ`7liYJ|P(tgzA7@0phg5M;9L^uz^ zv+N6a)&axcLvYk#H zH0SZGk)r2QXl@noHjDvPn^!!S(e=Foi)$DzxZr{gV9^8twXODm2wcW@!&Uf*Hg{@h zY5WTK(J=m}L-5}V15jcSXwm)}$cFbla>7sNx8do3sgK*w^~Uj;{aoS1dl8u9#gomo zZFbR_-@5?Taba87CgFV+$SBHKuRXgew0Y{|+pgj@p4W#!^o=X8yt4GkPkyq<4A)c` z&3$Q~8MLw5VWm2ms$g>JC)9X4&=3gPk#N46ca=jdB;6V_$a4_L$0~#{z7pf>7}3=m zTfTEVJBA#KdAu^XvBY}9CYa_q;L9(tX5lK;N{JpJXsi!D z(4_dkP%?}~A)v|a)(9bg=AB#c2AxY;@K=k!mg2sJ@_vc(9L_le<>f%jZ)T;!97xRR z+559SvULzRj20vY{DT~z29_0L#5TDKSbX}Y?0FW@-ii=1BU>HeAJ2f~Ne0Qb@F z_a^c)c%6M)M&5eV3Krb<$VFQSycPt)F)(c{n60i2jDE;^nGINU+ks$MKw)MftFB&q z#7o;n{7Ac`ZI=~3NcWis9(dqjW>M~ju6H2JC_D*g%N~G3yPBJi$8y~@nw6#_pE)rt(~a@oIh!q{sC;-Kv+cBSpUQ=ur0*Q6RAuWCje--|O; zBC1%_1Y2di*Uor|Q>67_T5W$ezWqM@qQcPpO#=mno8gp5yNCkhtCaUf(sWEnzhZni zt}7MhqO%+984$}Oa%`dj?gm)XEFLdt2d@UV958_Ai-Nl4k;s1>n(bXbz zZc|!`5FmWK#YvZA*d^#EtdH=Zs^cLBOVc?#f5bc!rj8Ee$GW($=@=y$OJE@p${<4D zV4?9?)|jyN?P9mfb$;$zG&T3K7~F}sZNh>aGg@hpx?3GGZQV}$H7k73S&w9ZDSRZjFDRYi_}+G0ROh$$ z2m89$hI$c*$Rv=JNJ>OQTc!8Th`1H9?l-^rO)}fkj@kGkqCh|*A$_DDWsR_>km~y;_cTbY9i{pdlrF?jA8ZrxMJq7sWbnwDzuqeAt&pJwDvSNAVY((+!?2p-x z*umajo=O{hjyz5R9v@O!NPZnU6CdG{0u*cr7$MH!`g@R;E$`h43!Yx`_^THH((+ zso8ST^cU4xri4&Q`w;4V26gbjs{|X?bQC$KqQK5CNhox&l=ZPpwt=Q@lsiLcDMLgP z(LuB9cYzap|FvzMKR{X`!XCb%;``;wCtWUaSdAc@4@gsjPOx} zmMDDG&J>tN1vzj5eJE;P#duEm7C~e!uD=P+fLtu~Id+nq!8MPPd5z@|_v&T()rOD8 zi_4Y;NB9%twMZ_mLKtL)uwKeU~#JoV!*+$m%ui8q!c0LWPsA>kRcl+okJz?`y9k zzTYsOlb4Ptc!ZDpT6!OG{jRspGMHXp%z&gp*V_X1;rAkt4j8J5%pRPH9p^{fsRK=f zCMJmi26+@f1O?Y50Ehx>4a&@V*oyih%x=L3S-{jg4r^;gHMgrt? z8-yn@@gTiNXxz&%r@0)h&I{XZw_Ong_!`@vUcomzf+W?xw8ZxaBbsX5OF6HnoKr~W zFNYs~xJZ6copsTbA)K0^eVg)cs5y_q9!~fa+?@$uR8n{9kaloeMBj^;n7|=8?q{>kg#Qc`RzbhX@_H*;TZK^iLYsWm}{~hu)u+)psd?G)%Atr_t#D^U-&oNhfMUufy4Q zYkfU|Bl>rQ@ojHH%V2toqW~gYM2JZM0D1081AGledvg#+LMAx_JP-V2=w*&YATYa6 z1EBNx?o=YZf)C_0x8>&fy6%nNh|E$sj6qrUq=6rWnY)`?-*-;WXYri~wpk>IEpH0~ zK?ekACA8w}VM?_bHmUC-t>qD-Rw49o#1~-lUj_+23d7E9?_d6b!0`-e6h(YY<BvDYgvkGpkI9}(?H zig^%eejdST9fd*>Hal|7mw*hg+CGIT%JNsrcp8G+g+w%+YTM5D;qZD8gsxv_KcKWV@@a(?roR;LMoQwlXy?O1tW+tCDS~acRT&v=I(61fo3}0>~gYMDMZ>9O=Lk zw>2~CF#-kJ2ux?iccVV{xzDW)?teGJ$NRv?D=~AwJmHGq>KQ0)o(DI+$n3{GjMw|w z#N<)Fd4qM2MaK0Uw9zV-WY`yq;RQz&-71}#5zCaK?-wcd2E$b=$GFx5L$4y{bv6fH}u?nrs5KrX(UEoLCVpZB=Wq6C# z$>$jqoX-)y0L{7wtAhvF=KK-9ad)i(!pLka5>)7g*_CCVi-@@yCQcaXqpg?Wy0Hdi zh&5i&_}Vb8>lHPGi>|_Y-}L60WihY55I|}*5xpr>Uw&IfP$1{uknq)n?}N#DKaA~% zV1U;YaR7j20K^F1{R;#Rzx&d?JiCSaKk?18d4U#{;T5@94hO3UjbuB1j^{GDddMf9 ziJU}KB@kbU^&ib_$vc7trZ1vRd5-6I;6?JUvuOB0>hd;rH93)cSNR2!D|*HPw!Smb zNVKtqYNUM|jQ@0y^Kz7h>X(}#VwZwA@22jrx6~exnzL zvq)Eg?tFx$_hOs84e5UnM7|PbS(|)40887&f1y2kobXRDySMSpANeNMs4Qw*kVL}; z1b}s1R<(DU>**9g)Zi(6Oq@6|9YToEnzoZv_^^#?zpp0LxM)D20f9wuM+KbHsv^$@mOM=;4$3G1d1ceu3tczbm7 zpJ296!#h>n1_D0~Kp#j0oB?tnaMoha`vhCA^Fq!uf zt~7OMh_fw(TW{OKv>L7k0Hz?!Jq(#qG8AF#nPGSqK6u0dGMVSQ$FI5jf`^ z(4Da$lWoB_YjFKH5YH6C7kKv<@c+MwKb^P)$-Tk9JA2*6`$zcZ9p2-Eo{Gd&%3Xke zllM=O##3CsMjDnO7OF@2c+{UnLPp?p151rTB8 zV0Yl#3KdSPaxX&jnN!3z!0{mS+YwUk=T?&#wJm4Ci{P5p;Ic(CX!F}q4O(H3(M5EXJ}flG_Vi*NiczcvkBgq6F- ze**#y2=od9!+{fV(2ErGY1$U1eG<&>2Hg8@IS}8ugwBk}6pM6*g0pOaz*6}UKH4ER zZ+_=H-x&jwc!-Q3NY|~`CUc(08oYZ0jg@idnf>6tj5$?8e$@MVzIh#jU>#@ez4zX? zQ-9Zcd>7$kEHfc<*ztpC-A=pv?ZUc1=Ft|>cWU7s0&T=eWnS2f8u5D$0ZvIy`-Hp5 z)vb?RgQM`lh6fp@f9a4wb|Xut*TX0zp#_59^9&j>8xUwfpr;UUVCh5gB9hj(Az;Xi zms$M0lQSaV?>4mW@8`UrBY8fD^?`vi4gEf72bTllI+t5!-8CpG{1iSkQL=0^!5@>M zFB7Z1AO_<<&O)p zkkuq|k=YHzvKd0j@%^9kBIOVWti%Gv#KeR6b_4NkiXcH%N*&B*j6)N%Z%yj)O%>@m zqv3lrXj35ocPO~PP`==^&wS=H*O1QT5Vh0UDQg+cvEob+-}jL-|JwdiEn7|couBoV zfYc=2xC{s=K!oth`2guLn!AietJ!Bi``L7_Fml8ZM`YJrQ=$1MpL}vUmnG6@gbfHZ zAkeQ65EmwoQpqzrWO|FxsSm+uS2qn|9Dj{%ySL#yr#9k7S$1R72f;cNf^KmbWZK~xav2ME+M zl>Lx=pC&1?6H$ynW3pXM%4t>vMD}Hn$?iNCfuv_OaR&X8@)aO3?V3nY{x9GyllAem zRT;_yAVhC74n7cjwcNy)h@}|@u5Ti}S9m$y2+W?eD1;!8Tm}qI>jxpk@;DVAu$`}b zmZOui4FY+q(Q3Fph5+Ur87K3mR9y#xDPOkz_S;u;VY7g2mtA&ATRVG*_%ZCs__~Z4G1(K&~FeZJ0orCoKdwYxO-F9gE$7Iy=|CbiPO(uvHH?E?jH&?S-c%S zG3wkaP?Vul`%?B*xc0ea_prsc^L&r|nTW-AhI-R6lpf3=-pz!X62Aq#x(Ph=5w8E~ zGu$YyuS5vhk!$ly^v)N31fL(w(d)0gAa10hXS(e zDF}k;U6&8i`VT_O@AHH}KwffV>)=IzLrMt2yeh#Og!^fTjzp-;EQrqO_%d5Jj;H+l zQ*IBFi@JJS>ry>I;=d*CgM?0zyYo?+@nDhqwF)6T@5`AG%OaxQ5yJ36u7$02>8|f2 z!bF^<_--FU*XHQDs#oRhV)9Sh^lMEbeYR`%e4On1>#t8I)SD@bT(b=~+%O3l-}=_K zG7lwWv1NAj(MM-2g3VZLnYK`-kH`e{YdITsxcu`xb0mnVu<<+S?Q^SLfo_^%ACp)Rt4k}EB%Nhc8*RIV(Gom(kpjWpp}1?IXesW+-3cy%;!?a=aVS>Y-QC^Y-TmbK z&VSY-nan(M%ijAU(5tC(JO;sWae@2=rB{f>rk0jzM!)8bD-7K3IP(4D&5bb=$3_Ds z_))G_)9w4k=l`2^-%YP_kI*Oh#4=txeOk*&q32VtDTs9U`WwzK5TlZlUY9aO$(D$6l+Km(>HiCzf=SS!nh+`+qn8 zzX9NZ8ykz$-`8s+<2Er2va$GkVIX9H+3^-4K$%C9*iVRk8?lJ_v3+&Y-^ahqZC^P? zNVJM}d%U1U;0ijb#!x~CJ2zk4PHQ2}2;u{lqmqATL7So8A?=ZOW|}%-bY|Kty8^%WsX$yA~&=ZOMfm}_6SZAG`4DguyDjEc5hc_otkSof|BFhdB9gY2^xa; zAsMbyoJ#s`dqJl{)2-qWKXI!7bqs&<2>!q1Iv@bBvEcarc)q#+)k*Wn`^sOlL2l7O zmV&(gd$n>5=De9@M|{LV12={>ROKwWYffqAicvJGpDaNJIY3mBc1&rB^XLw-JOh#4 zcfyP%p0nJtarO)yQfZRo(s@%L&ge!Sg;Hg#Mp*tlL{bv@xa>)H-v^^nPZNC=f2zCR zH2C5Z$0;$l`Sl%i)d&q+WLz=}E4hxf@~19kL*~N>;@43`>RWn^QcYIB=2@~UBqkPgWjkC>t0-09Rm3`as|V3>`t`gj zDDa!vQ{yli@QC4DVt%Yn2LVo<$UB!(35t@B{Sj7DelsCY(ujSReJQ0wB5F+)e8=4S z3L5ixdA}G#46EzJ4q7O3_s<*emR;7XSa9RZS4yLbFBz~YtDPI-z2V3e$(Kns@C9S@ z{&0#ciEx?vx?STdeea8FvY2}hJp?#fIZ>krtLxcJ zBU!Ev>Iyy&6U9E0_F56=!_L`j>vFSmT@xb=?qv3}ig1omedChaAUMG$vhx1yrQ}H%GZAmut@rFWdsfiHD zcz_?d`n*9PT-h&FdCjC$Zotr^LfyhL3*?MTi@iru2w8VKMipO-^8P)%@IR6f6nLhW zPv~7~Vqo{Zx5MM|W;;mmR%>%ORrG|pO*TI&#SQ(|>hZUhfoevGsOPHFFAzj@p`1o& z9&+R6UYbB=`3m6>GJ148r(H#cv{keE;AWYndz2q0R#VE)oFLtvx+B@dZ-@ze3yv8` zcuQabl`n?TW~RJCX*Y`Z=IM^o{LX#%zVpxy1zHug0|Dp))rmTsW`+LCBG%EB6C}F) z!Ick+wPz~W2nl4O^;`zK@n??S-qSHgIjI|w@N~mgJI>6eYafWbaOT58uLtD~KS6h$aWPrM_ z!N{`grt-eKd_wNqtG>b0oOhSZk1y*=$!t(zu|pTx+r>}IdL}Y7$w$?`|4!K?LqL&e z)l+1>hN0bbY2DI8`XzHU8CHvc?cOg5p0ka;G`N2BOlS<=DaLk;F31pu3RoW{&a zlxO-7Q3E@GFc}0%7hj^p65b)U+b|rM)r@yWo&)$%_^Oibfmzu*fd}sLXr;t>r$l^c z)C*JyAFfYoPDIQdP6cAvI{h917Zq zUfbr)`isKDT}!}(-07R;tmD2%Y)5sT$c6*TTi|q?azD3RqtRU+df$MgNEg~6P~pS7 z*rOsO+P9eIc?_kdLFU6@ib6|u_I&efkHLccv>ITNWO%ibbQnh&p!LI2J!O-S_%MJmY6J^C@t!V@w|ku2a*%v`J*2GqYdlPcbefx*5}TQnPQNMTU`g z`_L`4Us4qEJy4)#y-Rwq>(5KJRD5HJ-;)Y?VD@H?Pj4rEAP-e?=L>={LG3upVV-FQ z@=S!%Iy0FW;;RGVJW!_hFWFJC|J`O(liiC!a8X>dsY_OvLGLx2vBkwmSZDR?zKc+P zcf8Q!PW<79IHou%$aDa=MkN*+q+|e~%2#E6yC*V`H?ZhnZn^bUu@nV>GDa)=L`_ycS!a(_`a8-U z!*9F-8qTrnyJU*2D4FLO>-KfF-}`P;>*NjSVM>5jy3muszr!2&5d$tXm=sa<85yvB zudA~+(LmLMRP}6-IofgTCY5$+0HJ6TjTAP>(F1^tqG#nnJ-uW5{tbn&GvH9PP<`Xw zaynr$6i9+GS}8mWe40_l3T~i9uFcKCQ1*Him(MZVpH6OH!pR#=NaAmus! zUVA|+QA5W-(fTjceMeFgr7;Z{Z06oSQC5FTQ7ZJxJ6jLWq}qqNQ?!LXBh0sE^kRZ` z932)FH~wlnmj%U3EHkep-bLG36K^VqxAZ+Os~@5>Q_LS}Dw4QO8`|!DCrd#S zzSMr{Tq!cb2p9|fQ3NL)a>v&K$o`&75g0$|0|~gs>Dm#cqjlW0)1vqX(vTS~b0KdJ zTsww_zUQsSLBH>B6W9ua0LZu|f-@6V0-GO}ABMiU0Ppi9dDjN(R6KZ@-fsr*0UM5e z_%hnPg|d5$?-Y^`3`}A*8CB7i5Klw@5k~~(hF)h;3|MoqnITTntj$TE3MkRmw{6H= z>`F5TUpD4u%Twa8C<%$bTibKD_5AiyPxrb=g9Q@HTY6C`$UTlcNn$O=2~x>~GgDuL zCVC%!$IlRn-0e|%oV(4jyo2A}Q-6lq-9M$-HCYZrg?J+rLk2qIg2&R(e+v)i0}G3^ z=-@~Z=((cm`2mg(7ILG+2=ClVLzHj7i+BC1EJo4Q%m+btTRAX1(%2I+Id>dRUE9x& zd0q(y=J4T%8Eh&53N2YjeglV7p3u9d8#}y-?#mIe&a7f*em6=CDF^ULS@Htx?`kW2 zn2|xb(otWKKJv^3aXm@F!v&$jUO1{21;qHFz3E~~bvDDxV5$!GEN|io-)!A~A0r?_ zCM85HBkJvANHtIKmq#H9jio*AX(A86zA)XA2zMdljZbUqPiOjU6R!`DcST7ZN)*%* zA!@;aU@-Ogm@_K>Y8}H?eh4@Ykf!SVK^u}$X8|R^x=Q;VNRSa-x81keAMR&**>Nd& zGosX(U4Dr4>SOZU3_v2a=$)D7l_zwEI{m+WwCqK~Av?S)084bF{6+=U?rEZc1Y$jU zkef^_2&HXWF8gvtUN$jVqf%#6pxF)1kM>ZwqMzSke)3qoX!`h-d;({^b0gex60v=8 zS>4i?CS6A5)b9o}K|eyui6O$DT>>WQwAv*)IvaT#e!wBXdoy#AnX7^jF}bhS%|bTM zytA8J@x;*ca4hLH^RzI!Tot-!4S=H zne93fXZlt6K*s(E2QFk!v)RAI4#CYju5M4WJp|teo9IU143A`_@ITRH88m|L4sfL0 z_Wt$^PN@CI-hV`AoRlT(^Tg<_CLZec_zMj%m+Dg8#<2zCSy*DEz;{B3{3#19!{>7vMy~pCgd#`=o3*|}>O7c7}^b04o9_yVC zL5EW{k+0{RO7ewK#Nq>iVvC`Z{-RkD| zj<#pGGPS?AzdyH&T&<>+7Fq-cWbr{GWq7ZwKW=dJy6)F%Nq;-gAfnji9NG5ANDZm(G~^v*EVeD-WX z*Q^M?Ln}K8sZPEMJ$N7REpa_{PcZ7{NHWVuMr!87ggQaZFR35Co`zAT`xluP5fsy3h^&eltza^GX!$ z3#_YUJqDEW<~;+7b^JLVW+GG?KQ2J7Sv*$ioGSMT>Z->qn35t^KMUD!@6pDA->hNq zm3VpP-CXef9unYSp2z{0(i!UrT(Z)d&|ESzJ*ijon3AJSOl|#g{!_cLBv?O^1+K6C z?Y836vuVvh43^z!%6f`}Ysgtsj$j1IBXC zYNOsg!=VegKi%uBQHdY*YC-}t5$o3wfY`JYh&@)#vX;xm_ml@>q4kT?mBxwt{GHBrQ$$3kwG*G(vDrS;c*>Aj+WRg6pZ5eFp{i}&QBf8 zGPfBkfk7{qKpwgKpA)GV!|(OjK<$(F7z^brzvjE{MN?YM+FF+Dz%*&7DAig%4BRyv z6(aCbi5#S6xG2DPC+Wclbl91xcR=KNvch>cF z*1NaR`Li4#j`WMtuZw_!^$g1XfWq}&CTLEO!7u5~^x=fM?OyAr#@}dm4wo$%TCrEq zEsT!vdCT>h;qdu+?3(t2dMbJxy$aw^eoqJz>xv~_Aig~enIaC3ZEb^wiJiF2X_>G7 zR~sbp!ONx3HDah>O8ERm;;njJG|yuG&wIA}Uy~OD3Vr>2*n0X3wS7JifqGihbg(c8 z=7FLoW$5#a00hiQplB26U6>pa=#){l%Y7$XT-I-lh6*C7?-e($GiD65648EoyKgrz zW8k%nX|nd=X1B6zdz!6yvPEi(%HV79g*z=;`l#2A0%DUAAoaPKoR1r?W?CqjgN;&t ztn4RctPX8#W=NO~uT-v#)KaR&NHGV$!UZnYeTO0F$#weN@@Biwf*704504#%59oQW z9v5DAD;|R^oU_3ys07JHpA#9ai;){H14TpTJIF$(LO1}-FH0yOI<6mEoTHUbZ10BB zcmU%)6Lk#c)!$k%>+g5MKRPw zXc=T0(ik*@#&K_}^znIZhqTh>+mg;HMWQrB@P2}RYT}vKgH?c`=%mf1k5=?7@tt?k1E9hD8M$G;T!U2+K z<09`bGtw!s`LXC&zZ!!tZPr7NeY$Va5g;YyAb+$hF_Bnm=r>+GaNqI^oNviy4F)=< z+}MA!qu|CU@Zsco!(4&d3IDTWvE49=|MaP46r7Ms$58mqpDtyXv6#41P0D$=&CaJE zZJp}w=x3jLZ&<{AKcKl?N#9ACUY&@`$Z+Lg`DZq%vW}(JHJlfV2ySS#^)BTv8kzdvRZA(OK!n8RI)NnPnF=k* z%y>419{1Vlnw83)>*W?yP$g>iou!+h)L7HHTludxx%58{YO?Y8)||(M2Ca37Sw22B z%nL^7N&P_E{}&OriR&%i0GZ`?TGdHD`r{weIFHlVFDynCiBGYKAAC=s~Ytz zh1Ps5bsOog%|52e({o?BI6fWzAy&fvIyuTKj?2Hxo%{3`-5Pyj+vBTz=j8$cA4L0x}}{5M$q93=>KfduI) zDk?IF$ymx_l44k~>N2zosJ8Q)wx zbV0vqsv{5kgEv%pPY(@_ouvun&+9=`;w+vR$^|0;<%fy+`gM;P|MkJQY^ObyrGYDq z`=O6qC7&t1jo-rM*yr0f(aF_4hq*QHl(>fPs;jTTQ5=B| zH|n(?7eP4bmmCjA{gV5iTJnb@#3nUV44Pc{cT{Zt`D6 zpfTMXuh2&)x%EiB;>&vadE$i!_&{f|4;yY+06~y&*i-}QlgmUIfU;lQS|r3NH~G@D z$wy7pG9#1wSNWGbD`@D->6iBB)$^IN_EZ6pyMT4B4KeOVrHRVWT!rC(Nmjv!6_~;5 zeZFy#tr3Ly(Y~I>AJ$4lkE@{`g)sq%=#8_e_D_BzIz0Va%meleqmLnwR1cIX|>#g;MH}*C(Y*U?FM^tB_kG?RY2u?_UzY{g1lNQD@tHvHpl(5Bk7 zwY8N{fo8Se|E@Ue;yGFphbFEvPBBpH_Cbf{@jNfud~5Y&HOk(Cj3}WQwf~?^D*#zT zVtbC&n^!C9?jcr)^nat_N(Ckpd-*=IryV?nx0^x{FmA=-lmNCPVHdP7vQYgBLPVWe z)~0=PDZxw<0;R8)3XC~@xLz&Je)tNF0G^NJo_cLv3 z@Ffh1S*K*F@vek)AOyg~zqTd)xg@{2qU$?0o7ml!O)J0Hu=EyyCpu{?YjGAO*pE|-c7Ms^@%SXam!9Ftc z47pw5{`@Fh=OF%somu6)Tej>NAM$QXFNXRH*4rRdU}0s>AH`-j)Ak+4oyrT7GReu1 zn=duiW9fXUL!A}AwH=4nH6+x3j~gD%Ttvp&8u>!^qu3X+pM#ws7w1@h`eYgmL*Yt>`>>0LWMkv%3$ zf7{}#n1AE-y0ekdAY1aa&~siyiO2JnUEcTj)+zSC8>CQuljd zZj!_bqP%j4j*%s4qpo%1)MlHM zS>xg7-BhY%x&_i?z6+SU);n9izF?2#I{M~5 zD3T`mi>6;x*1oX%EibPnQHtJcY~Dw`yoPO)&~Gh>suMhOO78gz1Yr2p_!XxZCZ*l$PmP%TlPGao{VFVDS1(O+w7xmVc_r ziY*j-Mv#G0Kkjqpe7IO#nE9Ku*XzxqxAlZI;|lQVhvZs5&41?39&hT;C=Lxk*+6D`|VCw1r69j;K zy_We7u$k^WX-EK1xUc6-HkY{{q|gSNb;IqrS2y)CYRg7unE^e#M}62#agI_B*d()0 z*lX5=x~#M=a9IEzRX^6tERbY+aFSdX_9MEFIU8^OJ-CfIDJ)~=EqAf_tae_Xiu)6p zkQ7Vv2}jKLks5DNZ?)g!4=IF8g(o6!+ajvJlY1(A{u;#RedrU^M%M;2)Tp7yBsQG6 z*5E@9ma7+^FFDEx8;-2?b1N<4J}_-%Bi2{&FPB&Ql)2?kcyH!lzPV}Pmxt3YdsmRJ zMUdH&(HU>?J_~r$X2OK%V*cGLRUXhHjb*iXd<_nj?zcBG+%f*EP1X&3sNQkFS$o%6Zb1epX;$1LV2>;Gnl9U9}^j*RqC zdtK68{%GRBb(?se1k_9dPAtKaYWxfRh6zPLW{KJ>(l?_ z5dzmEoYns}`kTuxHL|0dl8{S3c&T={;5u|6_BJ`cxWamcoyooMZ5>@+QHOi@jCS2tb^(2b z1FUqxTBy~T7U+)Gy`gu-dmWwWj=oaB;R^Qe$kE!mnpm2wkBf_A0x3~K8s0a!(He+R zde7ihUMX?5&&3nmX!IT)qH}u!=sRc9NF^u2CNhD>)5P}RVI3|$9BK(`RdZeA{({y> z6O7kI5qcEyvO}Hyp0#%oe$MP0m$1oPzVT72-GUVC3n%}RpP=5!#SzV6XD@ay&XQ;3 zjpp2z9lcqQ3x~_|N=)B59y$L^cG6s^p!a63w>1q#P-G@C^B)?Ht7A{-MV$STy02;Q zqrQqZe{Nb?t!luALx$V%j)s|19)KCQmj;4GEHVG(9Z(^}y=vVFP~So5iu|unxoQVx zKh{m$(@$(X#mSI9DkRA2)ZxlS?7G_=FK}gg5G4J26AC{RJ0^R^uKwh{!hZUy2IdJZ z`6oC;t-oBIVBHG!B63L3A?x!GFiOxYCo>GE^Eu=)0Ut~7W> zp(k)o6#8A0^bjwJ?(??^hn4dPuq%Uea1MFmkWS@WEd&#+EYT^k7F@-2S&lz`6!h~D zht>L$^?IOrN+2ItWxP3(v&JIrfKRP-lv2(8eA*UUO)ks|dRzBEM9;vF2_okq5YzGf zDFRvlf*Z}XK1(2jR#SY%@p(J-Z0{J?h@;#Zt9(T6*RdgWm)M54^dtQ`bN$LMaoStO zx9~A(2-`M)FEz zRJHyk1zx%l5fG!zweH_?m{w}dUr>c&~z@7db#2}{x#k^NuO%7$G)U~ zz5Dq2V%f5}bog=$79Co~*f8`&UFm*If>7eD*w;Yk#b(-eE|YCHxi1>xhI}FZ;vsM$ zdZK_w;fB}ui`I$raYx6^H5*(5IkDgxuF%ott3*AwMXAl)z=VSoG71NOpu~f)?V3Gf?dFc!+mdhG}vxmVQY~aVaz8E&5S$^+&TZ-qz)&c zy9q8&FE7)rI==;h$AN{z_Q#u~?o#MKeV5Rd(Db~u4IQ6+StH8|<=kZP44w8t8xfJ{ z@eSy3z_XSc8ArwXN)vqGqAvcyUtR+l5?VCwt6dcR5 zG*a2>eitYY{=oe03)zX@y0um)gSIN$vwbw*+K*)rEZJ8BCUZU~H@qwaV4KMlsa(gU zJaGSeJ;Oy4iiM6Tp?!3PqP-`(cIhqHDR(<~OMl}8dVa*`i)}vn!(+(iH7lT= zm`Ft&D<1S|4c}+TCiw}4>_8cH{K>|AA3hbvjo*Z-X+-b(qP#fyOk0}gXqt6m{ya-t zH`P;rP=d}&o5yobTy?^mdj&+L^=iU)wF#fgIaroPp`#d)A9noa^eMDlH2h+Gui!L$ zmdkZdJpDOi^=>P=Ej#l=rlU4KDJR4|efcXj}?&n0V$Fj?83G4`tO@ zL_wL74y?jeIZ9FTLvjmhZ`p+Wn7Nd)v?rnBcfdujhDG5lWwwZTZ(`5-NZu3fht2FB zy^|&$eYn5hE?FV(frcAEE86j+a~1@8yYN>4{MwS;2Z1)t@UmNYL596mhK*s>85K==9Lo{VHWgXrTw== z$co@go%|2~%taX0CLKbj1Pi%sN*#eQZX<;Y9RYH`?Kn)1M|;L|iCFmqMU5NPr!Xv6 zl^bqSiD-l!DaM#7SYRsxthuwU6&fka8aCqZP(hz+4JQ=tBv^&{4IJ&`(R4}z1Aj&% zOKB(Na|bR5RwuWfn989ZdHKF~1Q>Pxh{c!E`cLDu6k*JB|JNaI$=E)$eJgudtU;4G z!($s8ixxwr&zLnqMc3`1i{Te$%CCl1Uj#%u>`%YZzJ2LUZa(3D(%T z1o-CTaJjOW`3SK3aN>1V8|PlEKg8s7ALlt$Z>qS+=UVeK%t`Q%`oaVBJsV2fznmYe z6&B{X$hG&YFavU1tt{mMd=4&R7Jda>D~KaJf;A?a4-#t8EtpPqig z#osGj^(0< z5CX{;wX!@>lW|}4&0PE>FS6cim~9StF|A8paYHErQG{BGrZIY5cw;BzY+n<=UfRdl zXW%+URuM5sE^!oaJ17_Hq6xzwk@80^GX(~$5fWeMV7G-T(;9>=I=TtG2*(Ek46$Ri z&-t8WzPy-mbSfyoj=J$T3oUUd4}1byHlos4d_5Z6y1eMdq~&bEQA<853UH%{kIg7i%6FaQvmYbHVi_gB+Lm`EJtoxz$e!v4Qd?jOEx81X0-@b2)^9$&+~kx zHS8}lEc8n5S`oMp2lY1=mdX5$J91(W93xeN#fjh9T3_RVWf&}YGglp&a^rZ$6r@I; z6PqH(b)U3{1-G>K*(X5WxWk{zX|DfuW5~k+>ep`e@B zbo2 zBz}p9J~k`#yxw`Xr}4HxRod3f@(q81j2^;kH&U|1qe@`zjr$HtUf?g+OQsdU- z4yIzDf5*Cg7Fg5Y?asv5m)H)kq^FHF7FAenJ|3NAswr+bB}Sg4%}&}yp=w^`!}@9V zoyb7U!|wx^FJ;nuc?1jxd_)fkB1*cX*}$NAB%@{foKqsKHl@~CViK;lRnf4H;`;{+r);gQRzN*3+LtwxV@mp*~+5`Va0HwlNS8d}fu zN>~i9T=Psgap+(18kDh4iNh9(a&S!>lokwL1dY?ggtd~rri}%;gCg_3?zvXsiBV#z zbXZ)~B82@59cb76y?YJ`xdhX~w+soReF*3^j}6+@uAGtpIwH_9^Dehz7(0L3oWF=n zurUc8vk5IdE~DD(#~Cwbl^M&7u7Owr;S_lK?J`4ahGTE6!dJb0q3a^*(a1Sir7OY# zOTK`G0`wKgJ?>RxNXmFHyP%|22{@XcD7$9T?vHQ^{&!N^+)ah~txc6q6mk?*MPVHR zom(Z8(F&fWd_n#va%Tk+HtJHkcdCu67s;MOU*Ki$+{7IzYsA< zw9G^Sz(lK?*L!Y!P~E|<)P!QL-C{1-GQNGovaO9F53Ss!{9(2`#S$MrZfAG-7NZb! ze%CGUy;>0PC`gX^x~=)Jnxa5!hO-@d1Ipm~W9l;?SiV|$iq-tz>T6osr7Tx!o+wvw zrDO^b*TRnH<`2#?VcWxyvg7q|nsbs+A#XC(STK?Z8s>C3**FqYw`od~+gkp=ODH5= zcw2V6rhat#ZL1^2t)amkCq*dWDmK+Esg{MTZf$q1)}b(Hv3)i8-FlGjip6x;MSNS_ zg8ln>PqX$lWJ52fK#pOUNK=9c5Tst9)tH1eH3RED5x7gbFl>pWz1_%IMwX`aMdNqf z2&yAQu15l>!bPAza#KY-S@#k$c%2HYst}7kbIFOK&fT0U3P?v_4b4Pgruh<5V14Df zRsOt9_Rgz}ML#Nl=`tttikU}7Nmee#g#(+s;klUpJvAWRc6L#Jn$*`nXg@X`&j$*L7+QH}zr5Y{7`DvwNO~b{=}nKg}EFn>x|03x8 zxnZ5S^m0-uW3||NQRa1)nJV-W%Tdu%(%b&)Vrs0JPGE4HQY<12VQ_Fc3AV2%R6qxX zTplTdQVcGa&G;i+SWZ(LEPE5}Ty9-7B30udm3LE0(PyShNSN{`R<}zIS^{`^+f;m$)(5?M)HI3P&0v?vO7~YS`g-`=Cf+$ z!7wxb$LccS?g_D)jV01!QGV4 z{cxM}liLP@WSV>9*Q5^jtyX+8-2Fb-Ut1C?K*#l9c@p8L$(Vv%uho*vrTMm`Iu<8I zVR=_}+=69}Eb*>*@&zz;elA?VgEW0`9uMmOL&a40MgayWI(|nEk>m_JyeIXJ2FPFuu!c z-k;lMTJb6KXaE{qp3Llm+wGydx}vUesmAN3^5*p|DTuDaSiv8} z#ZT^~@uyxFzKf(tE$qt|h|i|)*VC&Tme)GYM8V0>r#Ijn>0NuwP=&|62l&yOhHs04 z_B8^VQ05Cz326oqTSNC&qy0&JszCN2JyQNgm-Ts<)sN5CPR%5bICMhhxBR#8XU0JP zzr|0`4XgdD`gVe!5d)g<1jiEdS1h*S@Wmb#V14k9=B3V?4QYP}6buYM#DJ`^zrt zbX+6LGE^Ki93I0TjfCNe(}Y8wtm3hScMr<*VbJ&3v%g*(n}U`|0U&b63GS5A;n2T+ z&(JbHfad-CT@X_NOt>R;l$%z*R}0Bqp+XZuX@SL=D^#iu+Awpi*P2^!(}xD_Y+bhk)&{>?}O8hdeT&9>Wr~jTdox{h&wdO!Am=6lNaMnO-x|@ZO#4 z(dw@iF9FojO1~I*o$W>4DeD#@tA`jr%>rbsUGDwZ^hYqHBX3_1Bjx3&q~dJ* zEfVF6zrdzma((IApyx(P_~lj=)%mbP%bNG~C^BVu3*$i>ju&%kX6$@bUSW$0e$v@W zn83?9reI2Us|xx-dy6?${QV5l+NjDIbz4Nv=9jH{uYVDTZ;br+ipFIF-$D;g3~st7 z*0qI%pBKjqc7K!RU_GklJt~3z4sjebb{rf3`rBgiN_i-ey%2`8>xCZgmGUX;LGyxT zk2{KfvBco^{v@63h3$EZc1-S*&1ytrQFU>_o01%%ymy@xcz7Wc1T+EnAVzLR=;4cE zUrNJ+Xuzn}?fdSEPY!Wj4MIw;6NTUWW)^paS>_5pvd@e5j6>V2TchqMnoW22( z=2HILwPRL~A-DR>p^|+qpBsfvI6`=VEUe|XUB;5#XL_U{j*3^eT#j<1_KSV&b{u=^ z+(Y;ZB@*-X5Vt^q7QKC(0vu7z$^E{T8@W#OH!HiXlE(RhRKKts0(pzs=9;6Qb}L0$ z9~2SZK43J!y6k7TD?W>@ux{lcM(B4DA|}T19#4R)Mw8oa@=q8I9=|Nz*!`v*cGS{z zTLbSuaz^Fj(nH^=AAxXJF|c5QAeRwvva954q~W$mUrboM_>B>>Y`l5Rn}}HBEpEK! z@o^!kSpQ0U{`G<_o`uw!sV-w!*H6W!bH@huVx_PNx6*xXT$_3m$H!pH3ltu-$lrUP zl|ATXchg(kF}pgI1Xm=DXf2dV-dz0V{=;h{If&+0rAShYp+#mrEPoDD;;lC=q)aS+MOmWonB;m;rJEH9A>B z*C|ImbRV}Ij_9uy%>X?czhnlos$4HZ_!Z_7=ZZ;8I-5%=kVz5W5{{7d%>@2jeo{nZ zwzHsZpAY~Wz{LD^yBef52-tgfDV?_8HjMo+>}MU_>%f5ijR`!Ii2vc<6|9<@R$1%u zejp?AMV_?vnf`3oF|ya=u7=^xx&il*>tTUeTff1blICm-5nmlYkwr+shg>Iv2h29& zl7#Udt43|mZQ80cA-qXzuXq|e=W+Q5-y6^J4AZ&&_cI<=xGLGVQm}Crtrz+R(ZwlI z%M<%cJ@}2wK|2($KYxMpv>RrY2~&>alTcBrN1`C=Is3)oH|Z%tRXz({xy`GI!m6lY zI5<6QQG6^j#W?|lE@vv7pna*Ie@s6I#2JL#J4f0AAMKDq95IsVjo&Oc%b9e*pKgUw z&(SM2RQEB#zCJ`)tCU-l+!Dwc=5YqHS>uw=6t{odH_`1697l^kCzm|luQ-lQ|8^6( ztBf(tdVksXac94WD!p2=9)dp%BF|AL>;9_g<3Re_adSM};vpc6EE0joQAKu_J=7Lc zVn|YGG73g*e}-a(W9U(2IMOU);KBkS#9@cq*tOuRiB2T4mK1yivzvX9cfPW5^K0** zuiolI&-|?K-JX|v__E0sa|j9mxG`GjKrpnvK8-OHH`GI1s~V#tSMADJh!e4WC#ctn zxrA)1N`HSR?;w0U{1Eyo{yfC#Qne{05Y66aqJ6a+97ZCgZ6|$>@Q3kRrte#TnMkr* zJK6gd4D5jm=FZDE#aJn=9P%j^Cdsk5km9$e1p3EoBOLn=iS-3}mK&E@*0sSRdmBY1 zzo}WR_GPUZT9Rqb6*KuhMWcpRCGs|G5^K^Kp!h8XQpfv57L)e>TD#o+r43G@C9!I? zacA+nS)(mOZp*zEI`AeNDkkNzdM8B=Tv@6n-~2+&muv{${38>>Xi+2Zc=g{3y(c$!y=)?10}uSMYfPgOK`%nQ zGVu0$r_36mttG2dHBnC4s+fV`_E7NTF;(d!G7dAC0Nsb}az%m+9< zpeVMH0t5lB{L>u}fGJbLqiA%p-nHUPc`(^Fk;5vHcDI3oigBp~(Y*w7A%yUKL47*;j{0?6!N{|ZEzZA?8&tX~v}7+u z(6-@iPL{FyY7j$`gcWg)E_3+C!I=9h!P<_3wg8%>rDGo&tK=2$QpUT^uLG>!o2Xy) z2b^l?Tdrxh`qTW=xO$owGZfz=I6PH2LK_fB#Cq1tOf^RZlNOQl?EdHy9IQeddaRtCnx8fuy@FSz11Z66pp5vt-`rO3*YU0Ze@&c{2T5d||nl609Es zVPHgIQ({pPz<8^2&y71D?r19U^3Kx|F>S|jd1WX%{-ZpACE{++ zRnU@Ue!jidyw~)d)NPV4_Q>LW-y|;O=|Al*&%?~mO{4W~u4Y1QpoK&ggf~f7`ee|l z`Ls@l|C_jcD3^Xo!}pklZd3jKq)d->sK9@^`>H&RSNjbrgae}q|MP#P&8_vt?Ig!6 zrSu-@R;@-6?^(=xUfJg{Pn~6Ff-QF1%I8`1eN6fyl&k|@2&Xj|^jl1>N`aa6 z>{qLBdT>q5w<`48fJ)(c9t9u2fWB;YUj^?{boImxo{Flq*^8Wi5tQH6k`LI+_;y&; znUaZX;Ukc+TIc0AGOtss&T;adBjRCc;-c1LI6s)io;>Sg3)Z#2p*Pu#Tjv-3A+`Jr!rv@Y-x z@(ZmuPbeJNDY^LrKsc?Pq?A`#{v~<>>SN4^4~NGTWV7|ZhY%yi z8WUbB5|+mTfpSL|G44Se0J2A^i_lk)JZUu6nS|gwkk=XR$D<3bMKkBopZ45cy|1Pp zy^*tN1Q27USOnnK1{@tmUaS`}V&hmR%ExmTnq%BvMZfoR9^P2!0g-7ZSblnj=W8-@ zI<_a|*Pn}NJ}`a1nB7p*G%NUa=q1(J#h%ZtyM9Iz@ifWy8{gGGN;kPSn%Sj; z<#IH`9pjECTIyB9^0diOgz~JA8F)VC{SI}wsN?!^@+KaC0^rZCt?4uKqp0C67g0FG zntqpa6kGd)<>~=G{aaH0*8yrqSElm~_r!;Z2Qz8JBk5GEQ!a_QNI6a!O~zTus{YC5 zl^+hxQ03>o1bgKCx37Xh_^s>dkCZ`89hhPw>5) z&vzaX4G+bHgdQ1ncI%IlQwm!`MSP<+8sUJUtbp%lNo&*q6_H z)eN+hX=$7en5887U;55xAk_A$mWr@F`oeqBle)Km5bqWT*uOzV-&9&cx}OD7;x8=m zF7wE1zcM*q<>U*GX=*$)WDdTUUTPN)ItxJ~Yb(h0I;Fgif>;{-y<0{TC)-YOuDF51@p@y0^q4vo77cMa~r zoe(UL;O_1a+})Dk?yfQ`h#jotfJx1O6lt0joW|@~7m(%evX`jBx7IeQ?TaNdmQMZE@pK`ivm))C zF0*k3I?T|(cOGWb%Jodxw=cdX$!Qa&cvq?dKr8_e#6ehoom)WagCeoEDo%VVGs0#V zenb4Qd~>(r4Q0Qrm=&T9g42E>-t4;{$JVjmOJv1nyXkYI3%L?>U5W4CmohJP|8PDz zsiH1D%i%u~tq|m&I6BoFY>KwgmjTj#Rj*@5Zsuv;ff$UhZyX~>U#$|HobUauTGZH4DFif2@dBzY^e35bveauHDI7CUI}M$81L?U7d2UWb%ST`oJzYjt?pFn zcAgUT27jHJsgLKvuSUJc><~=s|Akx2IHyqTH#o?&&b z{{6nlb@)CwFztnHWkvt|)g#$bE}-!mYlMUbUYf%H+U7g;njY6TYpySrjba&~B}yny z2qx&rj^YP?Q2q{vsaWuYHYFQTj&DKZ!CxA)9hLpCQW_%=0si-p7_Vg2QT?XEp7?xy z+%7~>JGxn0fjQvjsiAyIk2H$h+m$3Sbf+JUv-oAbNw7r1b}@}`!THVWpC)`_WC|8n z2S_WOsJ05?UE~-+C!xTX!umJF#F;d?J?o>Qh`%Vwmau|pP+_psld8`T6aJr+Ng9~F zXQgN=Cl8c92{17Y|9!w6o#XF09Ycs5EZt`biTBkA-_K6w(RE_*=5sZ8*-v219+L7( z$m;EwSPB8p^lMhol-sGC_a}&V#hIsk3kek~FwX1A4Cs~MN%w0)h~atA3&^Dw-`49T zPuL$Pd1w;WM`ERWmn$dH=e(7T<{AITbJuXM3zN%-Y+Kkpb>b6k80`6Z zC8wOJE>QWJcl=t~p@<{L4if+tJkfP@o~{|&6_%~mWv8J0`V=hTh~wvy^m&H={-`O* z{$57(VM@5#|Ai^C9={gehExkzW0h)e&@*nMfgD8qAhWSJKbdMw>Hd`9w%W9nt*Jug z&F2Rd)QNME?vSDjvq3h`#+315Bjt8OL7l0T-JdFgo8Gyb#V)`v1RCR|#fTX3rNv-T z1XTUf;Iwx5rGV_@kd58HlNG7eq+1^J<2BM&t>dEMyXQfmb&Lz6GNn&0Nth1rs4G5$ z9~)R@pzd&lkd?|I^!cjz1TpGQ!J)_Ym)Fp>kl;Agl2Z^R0h_B}{WrQLveZYia*Fjn zSbKQvAFI0WF41VEqpVxAfW|`B1_b{~uDIs+r>#_VP`F|+=C)t>0ZM;*$Nu#dM(Pgq za4DZQY1>;bUbAp>5Jdbq>HnMv#cv-a%Q|T^pAFM$7?u3~gAbj17U7K7uj1f!fHQke zlDo#}P{0QE{gShrcz4#^8UO0 z+(qLBs#-+shp*r}-jJKBfqtKoik?qUdZA;?gJ)4b(hGrX$_k zFdlJSq_U}9#y_ObB~pp)FjLi$&TMq3I1J%kC7zI^aNAW606yIzk^X-h);5^R0rRap zN8%3`kX!F3gk8ku@8fE+;+yeQs^bH#W}?i3$tnc@Fb&1Cx%kD!4dyXU5dGfUzr1%D z2%5+oqpx#8i33mSC$ zI)#^(3=jGQ?p!XEcknmPd)9lTj$qgB1=mE5g2RHn*qbvf%MJXy+i0Wdt%ftpgl55`O7wyb4CrXXm~lK zK6nC121eVXq{V}ua6gEsSYI6&s@TtvdP94j@y#0b71Tp7*CXL!4&M`(*DXTx zxfee9uzu#D9fIqjm@;ateU33(+jW7a_?DVn(g#)2_@|MG*%NMhaH!?wIjkp(MMBxFFa< zJTd>j0=)`K=u3G`V(i*`mQmdg)WUF8bwnOH{ayae-~L{cMP;f| zQVfL9RU|9!@+Y}D)Y;bU#m#+5mc$hMMx*S<{WC5Tm2Vh&vK(~hl$Mt}BaZ4VlbUAC z*{b-0|7Ze-gX1Sj?mHuHLn}`%V0HAP?s~g&xbtMCw%p6si^Tw!9f#JS@Dy{t%4_v_ zdKI3mFv5a|W$05uJgL`7&2t!yy|`|x_t>q%rkQQm)TM^NLA88cN3zSNA<~sTgKHiP z_0o7)m8~>=)r{k(Ig}Ll`P=MWjE^Z>Sdx?~HvF2R=kE=*3u6_KGpD%{t!^aAYwS%Y za_dfdYxWGMd=EPu`UJuvu3NHP0ax^SkEijreF9NR&@T!RnV%_zSzk+lz4=2C`*`%p3Z=YtOy$B0VAwN9&Gv699w?%11|)RS;6 zvSGtvHkf{XwD~6Wf#MnTbr1VST+*zMy1A`681_8-2mR!dOc{ z=boJ>%nfz!z(Xbtw8P@knaTclb7S##sGpcj#Y{RN-vRUdCJEss!=ItEZx2_&%emW8EbaK`GPFZAbWb2m7b5(8g^X5arHP=WT#$S->fPgkkBZf_T5 zL~kT~IiJvo4Hf@!SD~&TFf7heDXYe+F6M=)z^Jf;QwZd}gFfF2i^6(JF_QHn2^#A* z+CzO$%~eVN-M^TPi&H!NtNywzD2Iq@&T!jP7?xlNSjGHm?o*0)ky+VPr$zbtpE(1k zapy!1y0xNb%5PkMW4a)GeC1yibUx{D=DzQJEe=b3+(v(wV160=x60`br}Z_2&cWd< z4-b6SJG>X*s*mZL{v;09CbTi_LOfK0XC7z2F?3WFikB7o{W_81Bi>1O#esGl_Bo0W z7uu)Kxj_p6+W!l--8ShAAe-7DKCjm4y0{}fjhl2GsO#yy`1YtXt9@cuGvPx&G3`B8 zTqe#$b<$2TuoZQog{(jw|9$$YslV?}8H*DuHpt#2$gFq-OX*gB(CFmmg7fn-;P`Uq zVwPQY|K29@!uR>BQ2*@e^4N^kslyLuYIKRrQ)IZbz9-6h>~Y(WSN8vS$}K-4+x5Ki z%0@vGc`77HaOovL7c|?rJgSy-_WYY>APQqMyJ4@zJ710b;rDtHCe&d1ceUZXU@<-^ ziqVC7K3EuPkUdAVJJ(bnN~CoKUmc}=M{)#v!Nn3aYE(+`8(b^hXurgXC%MF2Z)(@1 zAipBwIszQXMmQ?0{t=vKu%u&22@&C6L~WO!81-8X6DzI-!vueHs~c?Z>|8=nD9bOk zq`+S+7KX4E4`>pt1T;{2+xP{4V!mUq5^vT{bmK0uUz#)P*ZpQ-$|h^;%668Cw!1(S zS19{P?p}!9$4MNMqXItrn%%w+A!mE4&iP?uokVkaF%s-JRS60^lG#aDB_#+NT$q5O z>O|gDyV%={38=5{mQy(e@^llM))l2xuQT%GeBi5H8fi))g>O$(K$^&o>UCjN6WyzJ zpZ|dX2C>vT%Y0i=h*t)-EQ6wI$}h8pKTxMx)QD6e%c1B%ki^bBtA9 z?6xt*KB>rncOhfeB^KE(;V(^A`wmGRmUW#DbX((JMqd+=VFR{5Ib14IE=FgV8+a$5 zm9vv!n`}!8ei!|ts**2RaO3 zF0O2bbTNqee}_`~b|Jl_CM>DGujLM-sYUQGqB z?`QR4^EB8(9CkmJD(W_)N5eBI?luk_D+S8hFzD1Kt*GSNrcGETXj@k}1KTbj#Khl7 zsH||xH|=VF=IbhJ&(Uze_4p3ZPEmRho?rQH`98e}0%B4+`N!yvD4LWexeRjELEU z8u3SYwv##cG=Z0zEM(w)R}KZ}e1zvSWOcyqE>1#hY8M6lPq8akxfcEldQLKl_krK0 z4DTg#3aQ|CBu8Hvk4yzz9XCtiHRI$YE5=zA)`qITp*W?65q6c+BV0P9A@4`5kON>S zAY`E`&WIUx{>Vjm3(+(>IiFIL(MX`DG*}-70zA|TCV(Sf`I$Y-ID^}(@=_$K&E8}J znG59rI~uY%d1K79c`SQ_7c8J-=O*gpMl#pa>y-)Q!@d3}L_8fM@t<4<5yt^C&-m{T z7-8U7rr>+J{)Tm$g4Oa4kGAr?FQA*sB8M;G8Cjo;H!OD%*V^*a-40)a?yg?Ac=mn5 zyrgu5GW(<$#~!$B!YmlJ?ngGBY=ct;J?5}P68vkgN8UEWh@oQf;ez+y?biq6{W-cPOt4@=TpB-#vAZsm}Q?JJJ=%NsjsUQ0~YWm)c@DA=Mqk*I&%V0Wn~N$^CCt z*C;dZKh-e0te)EiLYgIUXvxI)JqQY-yhc=~)=hgVInt(Io&Dx)8OT8{yamAtfU@2Tmt>|oQkD}1#&2Epd zY`DvMVomxcmlp3<;rgl#QkQrD_CD4kdV#FP(PCg%2^6SdZB8|7Aoo8m zS4gbQqnX)-!gFInolhVVy-41FypNN#e2*}`YK)B9)i}%ArSD;G=SoRF>4#sXfJCco zlI8Y4>WaA8x3J!V12r#1|G8axaB973zk$uY-Jwl{Fg)i&N~0XHtYH=t%oG^j|m7+1It-`zwHj% zQR-<^HwchiYd~p_8lXcVdwya9)*qn%Fl16!J7eR*_OuBJ?bgpImjPgyP(b99a3fFm zZ%bnXe+q|}WHRQ`hIhWqQD8_DIy<4-7q!53>vLp)tysW;HB}^>(ZkV9hwaDW{V$Uw z%*(foI|3$AIq0L2um0ZmwfB$doS7qCa=PEFL_>h?!%_|J{?b}U=A9);HrmEn=%Tw& z6)SqRMg5bpgvkOJ^u&RKZ+1AA2=MOvUv-z{=(05;{yn2mN(v&k^lQg_hbN&ZY;+t| zsJ9eLy=XY3k1R=4*1O!}s8pro*nckSCT(rJJgg1!hhL%j^yND+b09PAd_iJqt@;ZP z)M1BG49)0V#v|PL51RjLyfkecF@$j9O7l=vbhi*Y?D(g^K3EjW+#y#aR2Nb<`+a(R#%=bl%HWHv{#_8{60amm-cFFeN6| zisg;!t%!o*C+d%TqldazJ9u(2M6LoWtH`HBKFLr-y-WBbGZ^*mc;mDgp!cpX+T?kzNHxQ3|29LxhEds)ZhC>=OF)p=PvDR zAGWYWI`qMBNJ7f6&=Yb_jWGo=>Rut}D z@cJG?Yb08lXWYa5qS*0DEfOM-E3?A+_k5eW9F8c40iz+jU5wEX_#qPA0VT8NMGhiC z_KXT&SS=6#c2RgM?yFZm0q7IcF?kInCoKD1g>KS*5Z0l(#b6bDOQl$E^Pdm8Gn7>s z)W`zxBKMP&{N=MuXqt3!3QdX-h$KF_PC;t6@57;{J8~hz_zl5H*Z_VLlx% zr}h&zVAPGtI7qZS7Gf{deoQ; zAZJd^(EWa{am&-?Xq`6TwYQdhP`^(N6;D-V@ZVt^rq?6eaV$IXo+f#(6{eu|?&=r& z$0^#^t?c@&g+r^~ZCF{f8-T0I==!mk%?FuiF!a1M=Yi?Y0mDJG`f2oD)uvX$mZ4Ii zE~y@G4$))mj2L+>U>&hbU~QZMguFOUv9V6mACuzGt6=?Nmoo5@fM45xOjUFWW6*uH zLx#vIDsdq0rP0#S_OI~^eGo1#Y%J-|Vs$tiE`40ePQ)N*TxGKdKAGOLk*QFZGx(q5 zIl_Xgh8)*>R)bX%(vqfd#+HH~w+}6ZEZqy|ZB{yVljPHX*qtMTdO(uRD zx9Xo4J@}hjhGM{Yq()1uh2VUzKz!E4eW-(Z6jQcvy4W+=S^~_soWItZaGayOgZOr> z%{^qbBr(=6Rf|Go=;J>ST@PMLIeH=G>F#PWNp1R>M%CB#3 zJR`Sp(2+vqe5iAN3_SfNC5)5#U&~s3H35qx3EXFEb1VNFX@0*kt)4c0e$+v z?rg9e>%d3Q^ZM)a`V*k`@i9+S;`x0BxV%&f#Rb|vbQf!4*++QTpHd*-1s^Opu7~%x z5fqxnPiSWGbC`ikwjBw>TPbs`OQK)=YQ{9q8IyiPa;|Wmt^zFXY0im+vqrH{z~9Lc z&K+ZE|NL&_n=S2R)B0{EjD9>M`r=^T^?Y-z_@6^g`W^mfCir;YP05$PlxA|>APOlO zq9>-WM!e&+hd|p30k@;t_%x35EOS){YP9U!zE^&H0zDne-KF*pZM#!XycFvw02d-Z zKXa8dS|_8DfFw|s_qe{Cl7cPeYb$G-!j<-YSFjj9WaAFAN-{C29P%Q)a0dla#p^KO z8htMY@aOq7i{O}KqrwFh6Qm0jWiv9&dDq)H*Z#5v!vmTz8c`-_ zr7u(G-=;gAEt`s;L_do&`M{uopPN9{&LwwSalj3pJpmA8vyQJa?x`plsyU`N@{z^&n}Heyd5|qt;mP9+ddN6zO66h>a7`fJl-g-P}6%2G&f2tAZvWwix&rWsRpTN?3lZ+xI_1$Ogf$-P4x z)rPJr5_k4y`y4|gRBDx9t9uY#eEl$B&uB$h$UGhB73ZZCcsu`R4RRWsUx*70xd zNKJ+`*-3Pm9mFsB5jE`Ar>KJz>j-JLdclenl*Q2)T&vVVlLIuF=v$%C9>V4t!46P0-^ zDuTOW3eop#=KN%oaer1nm0V1xcGmYU{r3Hu5aeZI>Dg0Cqm6X~=0TZ2WX)4F3^My@(rI;R%{!#!X0h($ESTP)X zlZdQB0HM`I7^VKN#Y9fWE}QQ~6g9{Wc*K*fXG+i$-Czb1+*sUx;6!o~`vQs$3le$N zTn;m7^qy^2=FjAkJ)XSnqkqZB-fJ502r<8CqCg~?f#1>!7XaD8vPR~=oz$ory5xY9 z===xh(A0Z)jh?pfwHL?}V$EFgjuG*tT4$j*YDPG1@}#k9Xntqc}J4#0vf3=xB}hJbanD-dHI1cyfKdUG1q z0NyX>RP9?xZtq%yDYTN_wSJswog4!++~w^3NNwe~r8yYD;DTep%{t}-8*(&+DN;$L zK<=)-Qb#cNcxGhMCm$b*TEr7k)whf(xP+ z3hdsIV1^Qm@^eBe*XYwc$j2G2(6lVR&!;(`h3sxy1;-~x@EToZ%zp;ZyPsVE%fc|b zU4lzIs*+T2R8CS&1Z)6gZs0~NjWc-cWcH)VSBMqk_twq!o}4&4iFUt!K{5?+Zy3D6 zaoAGff_G;cSi$p99=NmUDcDoMptXU$=ohbw9mAX>K2oxJtfwQE6HB3Bv%YbCzahaj zHU6FNlD|lPXTZmQ+TPKnS!a~^yfv_E*a|8P(K~6$WfZ-hlx*mqU$Ot=n^&g#!-veE zIi?;gBE%}fe0y0577KSh%cwXI@=LFKyAygSHryQ(r9l*H7zLm6!CSh={D~8%+D0C` zZj~K3OZqkZ*+u0J&>keqtR^GV8ROmb32%${eh+!hqZsgjRJbvkis=f-bYd6X48~5_ z*c2vL5xECkk6G(GkFTL?ch9&2ktuOtk&$BU(TROlePHreE1oypCX}I#pa-o?2jMVI zy6T(7aIgI#8?^jEF~ltbkT5OZq+uUW+3@ry&`U$NHJJ2G1!A-#k!C)b;xt4$7wxyc}$`9}h&&0bqinTL~FepA- zD1d%SjLrL}oxgFnyD%&6XwlqjX;R63?>1ZZE6r}oo28;Mt!I3(QXE(`Ih9+ig$n=b zP*zP)b`ZoxQjr|#?hrDP{6arHZ`fyH=6~ z?+F}YH{z9%1HzfSEh=`AF(lh3iaL!iPtPcHH~XH+P#}6@D_3eHkAEp1#)t^KM z4U0yiaD{-`T#Q-<+IBt=OT69;{%T{pXvlN;xdxyeYI~&3&-oH8e=3fyFi9BYN+M;dJsQRv>c-L;+Du%*FiOA+$~zgm&$;#N zKU-;WVv}lq!tTQ5BJ#GuwRq5R#Eq=0UW${wt9A;o9|Sy!dBI}05U@mezkvkn=DV3j zy<$Q%dxRvEj7Y*er741r|31A4alJc^??l;T?$xCD4?PE)Wwkccf8F$G_NRBoh|pF> zA$gxDm)%->SDoDXMA3S%_JV4+>M&X_X`l>qOC)#QZWyjAp!xy|RDl@8U7LP#$B=P6 z;RAdpZm%>3mpv|R*@S^jMMGl%t4N%Xjo9msVHtMryJso|xh?;J_inyOI61iGL@ryG ztf{b~BUojbHSSLcJ=;W`6{&QiWjOD*jp{ZGYj%DOw@LqVP=JA#1!{UXfj9)@VU@iYT)%581%>y?;y%XF=ob>&^G)AeTONXtg#* zT4(Sf2Q{s$6;z~JDGWyi4*lSkNz(p5*C84ge#t|dehNow$s<+E$akQl<4>B<8CTB5rKuTypsS9ZxqDDG*O&ydl5NI0lp5abB9>Vqr{ zQVCz)E5{#P{oj8!38ufy-%vUgV)7kw>PxUw8k->UroU5%{tA$|k`nQn(R|UQ9PXF; zUaOgi4a>0>3BT$tI2F5JlKSnF1O!xL3O=B$iLHm++)%EirwKFaYrfrt$N>QqfQ$Ug zzCMLB{3}s&(R&w4G02^|;49HV(ofO}|f&?1`@4$*@q{uWFUqdUb8G)tpJVdlke-vVfn z1mNX68=MK0>Pcu`Gpvk@Y*qgEQ~&oZr~1DO@W0>r?X~y+{DIvYq)5P&yw9(~#S6BN zv@UM3^6u2>_GWj-ND&dKI96tv8v0~CQ2%>!=+pYh-2p{4*Au;i#WlN;kplge^mV>) zMSqe51E$z!q1Ya|W+46P)2ln>K(VVD@EC?ez66$cAzbfO>etIAP4 zIDO&z+8iR2=ii!2nX8ZkDBm{@ZM%8#O_}01*VnT@VBT-J5dv@MEDTv3KjOIcQ_~%` zzTa>Gc^H^WoZAqa>oPR6OaI2m-`2W|rtina(>yps|2`g~J+H z9|S&?;J%HFm5DmWGs-lJf;4}kS7wOH?}@dqCP)mD&kgS=z2G|}UL>n! zqPqsP?Sa`9c@h6tuajGsf_-5j+yT>chbcDhH%cPzPh)Ym6Q_Qp0-6iv%L-X`y*VG2 zSmEWZ^Q~s)!R0H~<3{{r43aMxNWD$&6YX!RR08_fxe^BFc2_v-o0k05@V$JkV(Mi${Lial#tV2EB^ajE-?=2nV#-iq z4X7srYyyn8bT%?4VCM_+)ue${#)5?jpfn=J=oOxAwzris8f8e?JfBuF0Kv1cqh(-S zQy<83aFJaX-JSKHZtmFVMzi%nV|#iAe>^eKn!&`-Q~Acp==5jac&Nv_A#^6U+X!`z z?EEYYpW$Hb_Zs>T9h0`rZ#3v^gx~K_-a{P3h^T=^hBRBBk}H2;7eFaia&RdWC-wKc4L2G&71M~T1eRQ1u?@>Ke8Asz`lx^1@7-C*bSQ`Y&g@xqP_uBLjl?Z>sC7Nq z4hOsPhU2IiohHL$&?_3TvSA_-Z`;thXMHQ$u-f@nj{FIYUJ@xe+c^(@$|&LvO(6Gj%43Gt$mOqQ zQx4D9CFuseuXymEl@RBTo@ft#6U9htRp@CP0<_J4p2Eh}CgQl%J|wST>?@gjkz3)5 ze(cwcMJ4aLmf2i*wCN9X9(S%qr02i2HgqxPB@RFa*sso5P|d?3&bLN(A6$uOL}LJV zu7$8JFJ#mE^T`}P2zC|${vgc>$8Y2)S#6L< zeuR0w7Z~({i!EU-6Q85F??va?+vK*@gy(FgaPXUR*YK_ZTGrQOQCUAN?`t{Aw7MWE zwx-#`7OXAYowcbpctoWl{BJhqYH$1z>96RpCBRUEAN3E0uCZo)7@XZ;~c}%Q&;}KgV^STf-jSR;)cwQi%f4(uN>o5>7l1m?!-1 z$9I?TPWVbf+8D4Y`24VcfV(gHep?Z7ZB7x(6?VPRl>M70Ah~0@O96V3a0+&SEqU=$ z@_63$`eg{A_oN{jH6l%qCihU109aNleGd&iN%&rP$7w9O;QTR=C*>=O=T?(pkX^_4 zm$Hh+86unV8uaM)TVPmllRd4(4k`!Q|NF6Y&U5#ZnxU76VX8QTLAOU#vul_P-AzFO zEnqFD3TpFyNNual8$v6=0P=}gVo31|SLK-*y8Ap&oyGW)DUh1xDIy=Y2Jq5{Zr}J6 z{a_t{7Rs6Q;`W~P82?GTLp9s>MZsy>Yua<^X(ORSIek!@I37gtSVqzW+tCFu zaS~>U(_gHC$0uzlRvsr0xgjgmbgAc!%iqvJ(&mLRJR{eN-kg`_ioTqe(w*-~;6NZ_ z0&CYdJ}of8k{N^-_Sy(QszqgOhJ1XqNw%qMu?R&h!H636xFf$(vV-ua6pc}k1m4e!SoSjQ1!u?ABc!{w@vb_{yYm54>1CzEMyICtB?TUUh=YGs`?hTQzO%e{! zR~G^HbQ)ACiuk-(XtxUsHa6=1YTvSH#Ze(NfjR~iKK3M%sdAVM5W#V=riEL8nqv5! z_MYAazRaZf=fFE0tR{+az{jWUBuNbSOR`wYDT0a_80jVAYicL*P-pV#*u*0)EZi5I z2Ey!@ZR?bLW|Vj<%7KB_;vlhIR=abt^EOgI+gU49%v7db=VHxa+Bksx|C0Ri_r2kU zIj<0~J1ym0d=NHUt~tz-TGPalB5C80ZW~AIuj_80Ja_cPX;(1FgsD7j|08gG5*PBd zjK{!+kN3+^LxLSl&om1n%zdhp?Y{FR`@S=P>eYYus%LA^z5UH}ENhAgKkj4rb}xsT z2K!L^Hw}sK;q|ZcB0nVVR1=ItkT*3DheptTbP%dmg^4o_EQb!}hmmER){e3+Issmy zqqn$|r4Wy)f&1dg_&ko6S1}(hiSa644Kp@wnm<=Z7FK%@_VTb}?0HWqm(RB9FZU1BD{KfPCY`K{tRJ>UQPgmA)r zkc3vE%TzH-xw8$5bxDB#0mi4CmXD{j%g3O5Pd;Iu6=p zQ`zsFdrWxM?hEbnC*!woAmB3J|J3qG6)Jb$#(=?9WaXctu>Jgc>-U;!3_J?=)}`4g zh90}g);R?x!*?AdHoX}4TB(WL%=h`s-~D2e!IWnQL2e8?k$h#{LH*nw=csDe9modD z5x)z*{!vTWJ@=e%s|9rJzXGfEVen@hP4BfLY$Ph2CB?fPD9+VoUA*6>?&CleT6zi- zeKNfP#^O`jWwEHiS6z9sf~4)ONnE!dg$KM;z-)|D(&osR`E94nA0x}yzQk&6yJ3*( zq>4M@XvM7lE#D>rpN#+(O9Qn7Q159_0k+P=G&*`fanCUF$I{q{8GqdmM%;>TSp%1a zu>vD;>Z_j z91)|)hCM4T8Ji~f)rVUk$>s^RI!LPbeGDpPy1B~B4A3}NC^S6rFS0ZEWfaES6ArsAh!Wm#N5^whb6jfc@b-KM( zlKE}K7aByq1B^U|aGPyFZ7P)i`4O%N<*w&hU`b?c)T2_MuojmfK%(nVJt-^fNjpc- z=mJs5Iqy5mpyO+i7Em#+DQe4qc0R}Ye*w@8upt|bi~09Zsr*C)=w1K#0VzIFvAJHRT8lb(=`U)8f)68U*=+TRBYcC&_N<;Ek$#?Jq?UkJMlT`%cP)j)(5 zjZNV;%6R?Ubf|R%kTR@Fsmyog+2@%C=iy~$dgUEEG;AC>8|LH%&kPO-kCv}47c5a1 zpY%p%AGCwD`>D91E^rqSR$ytT>#rt6MVEnKfSG1{HsW5WcFzjNt^L#h?M2 zY%5X07<-i5-n;VH!D!YuYSUkGgL=4+I;lPBGo?UCMu$mOb=H< zU&i}S7~w0P3ki<;#57wRx*!9m#pD#8R5_CWU|!uq;^5x#Kb>NX&ZKp|*hJdZM~j(+{v&@-E!% z*LLA^u%5vp+4Tsr;wVc%$WNQo%JWE%eG!D?$$tqc978chW@S|nZr8{AuZ>NIW8R>O zP7=fCzTOy~(`&<91Q8Bd)sXop*B&GY-Yd!1TkQ%8N9ADp3tI^ zEvZ@i)`X#3t%Wkr=;4#J2NE$xqhHf0LhvrCOyi=yAwxa@C*16yJH_zRjFV%&cU}C% zdPn$1?xyH5Ca=N8B&yjI3SfnOqxEg^Bp=Att;$n+fP(X|C9H!04& zlVjNDZAO#7@rLy*3AJJxrk879p6wvNER_}9gTjY`ZK$Q%4TGpFTU@m^`yL5hLrlnn zplTjK-p&f&ZqmnJXaB5X69XEOPM>q4yS9l5kKp`ZwPF1tCBi0PwbA}vthjH)L2Jic zy{%t`2{#3FawE3`9lmz}`7o6R-WLNgKx9>ZFQ0>f0mf-tT+I|5NgG2J#$3^R9}^I~ zaJ(XZidk9iN&H2x=Ws*{OSku%c-@wz;` zU(9MzF<05#ANOGN@;qCyU0d71Hu)+1 zL+3I*l$NA5BkUPNGq>oO%t9NKVy8{0wE625T{$s;!7WIMH8f#ChW*8gXq}!zO;V-q zy;2F0k&dhUbwNz$v2@AXy|z}V8&uuK@eJ30UJ}FF=LxtI-IQFF+od)(`5buWti$r! z1pawxY^IbCy`YuymMe)8XDMo!cVA;eH#Od;Uk)~L;E==m!CES!ng#(+%%0xGP(*^q zW*4#_UkuGvJ$w3=$(@;z1HfG?1w!RER@YMfDd=L{k1~Q=5q;O*WsR(*>;C^N08x07 zb9iYde?R7r*R!NU($?tKNIMHptjZ{cp1TeJ^7yjV_{rfvoyV@Ta$6eKo*Y);^Yjd*^w2O@{6p91wtywA z67+WgcO|xWf2tjK*Pr%ynY|W(eo|x==cC`mMh(>9`sDi_}dfISAvgj&c{xlmq zxE%?`K6ox9CcMu3J!^k3A_WK~ySs^4ux3aB-J<)jB>vy);&@K;&G}Ovy(fa#n5`}< z7npDrY6P&P0CjvlwmmOw)fDB1SANV3POpq_FEZWdG#u__v>z;_f?(El8YD?<%+D6y zC;h{p9O-+cD3#n?**BH<1=~uJw-ND=n-gVHDLgEiM6ccq_M? zB2*O+@?s1&(@)yBw#NWV-8Y18tpgOx`Ix6vfkrH;COXFR$<(&5_1%5ZiD;#J~Y8&?IMV9FXu0Sm0 z=WeEtf}#fd34#-amWL9p?P{Bv6h-vmpvUg~%@on}%SUur48Tt@u}A*2fy5LYRZ56| zZn@$?RVTILfEmUeE!8KGF2+OV!=Q{}#>rk1YKyYI#WI;ll}{J3*$L#~5GAS_Z(uiraXF{7jg?nd zl*z58{AZe#-Y`&@@YGhetN1+JyB$N;4o}p;q2t`A`byTX{_l@Blg#+FgZ_B{7m&x}=Y(nWi;!GlA!4E18v zytz#65(;WAMlMZBD7eagqX-=i>ZnjTc?3j{dS)}@cAVdPS^p8b$exPTW~XVxv7s3$ z&&Pej{;@eoL#OS@0#RZxwMO90#gA~;V-@$Z41B8lalC3(Ik&HMg_ugMWoTg`dNL7t z0PFUI_r(lX=#ws4Mq_k`4k7mD37`{JSRb|gFl3&G?6(lSxBGsoH3R1fj(a^2U7y4cgv_ir#0@#5S2 z*bVF9k=qU*L2R#vC0Gyr_M|3_u@zu3CrBUi6J(6cfZU()#YPB zC6yy10e?%(lhcJ4<+WFGD=rfKEE*V`>STD+l38>-^t1rbjE~cyS+wvnzpB^rvhj-% z$s1Hue_Bqt#))BWaLkMqj+e|dT7XQF=tOo@!8cy|+*@JP2U>X;{cLF>CxT_5zx&nB zOaJV5p5!LcdCIr{KzuJx-$Er@hCjwe<>ztze>9zAdz@Xvwqx6N!^XDlq_K^OZQG4) zHg={!F)MqTWcNIk85_5!x-2@4D7>#gjg#Urn-pD-y`ijO#4RS zfl->+@@oHr&q%&6Jmu{S9SWAcE|6~nW@qZ12agZ`JKYGakE@lOAZ1|~Y7`vB$TIOs zIpIll`9T4(Xe3Vu^GutRMIG11%k5CqDSelH)n*LkD2vW{Rm;-0H8%4E;D6(_<$I!e zVVzL&VgQ5u&fpVXMXH5z-e~TY?>%rl>%o5|7=&*}DH*;GWsj(Jm-Ho8_a(AeL}Hg> zh9T?!3#%=xelTMkpz8xLM?H_|pFV*I4K24I`fWh?Fz1c*)l)=&+0_>oCb_f7&I2e` zqtujb^Yi|eoH(Zx^OCl~_7m3;i{3l}p%2KM@cYLZD8ngU9&sJVJ|%Ny3*V^!;s}}-op400m=_7Mj&vmM+K6xFd>s7g66t12 zJg8eQf**^ZaDw9F`;l;OzhN9b$2IKrEHfq_lUZEnI#sH*xZ>{+PhRCY7t+PVs+p0& zXTz_{2+wq6t-lt7A{F9Cng1l0Nt997K8N1FiIL*&*{}R9fQI|?+X-c?s)A%jC0r?g zXnot<81HX`DRUIUq0*syI6(xdyD=}hoA|=&v(-+!i3%~YSt~d%AfNX#x7qW|J#>x{ z8p3ZMiXqBi>NA08hfJD3`EK@(^y65XHzc=&NxDy-96~$*w9Rw?M=MX~eKP_V$t!($ zsIacio6DXr1~p8yL){A+9d)J7;U!#EUqRahAxW2_W3MIyApR;wjjB#;Q2-eO0Pn9} zOZ{@Gu!rXYw7>f};eUsWSSloV8t=9t;uIR)@ke_3LOIzQR$QEyGXGW2?<4Ka;0X@e zks8mXG`<7nlW9h}3xMpt!D>+!Y)?|YjA?_;+S4vf4C9SS-!)0L$VPsppPD6#|00Dr zFkl%RDA;MS@E#`wiL&uStiwZz{ad)9)65j>SiLZYaqOE>J8Ta-z zEb!6ZT)uW1|9bKZc)Wa%AT3BS^4$}@>-x*eZR>)zwc6;j?mSY)Iv&5z9ZcvOLb(=1 z*yA-)83Ld&`|(L#;F{mU^r>^p1ocO&r2B=xS7WZD8z7@k=srR%3);tf7fKM^IrJN2 zjY?*`5%gA?o%Xdg^sywldri2~jSvF9P=U{8dWJ4wj=he&-#A=*3154ImPM1zmau&NH{<}+}Lk0x^h_}<&dz4RQ80S6gG9Oew7`E zaLQLLGOOmXqCunjpDwF56&B1NU*~!7K|~riUw|vS%TP2pG_D=AeIW^~pFu9PaPQY* zswn-4*vcxr@6eJzM6e`8B_yFx(NM9{#8JS%gn6OCMIFRs#4twv?VH}d0&L5v^DI0( zJv=NuEZZ4QF7huu-Q5>_HLf%k)_ffc9w?Y!c#a_Rqi5fF8Awwrba1(HSUotb4&Oi` z9`$N4e`~C&|AFMhs#(X&Y0Qck5qB?W^3*F-0+*lm0*`o>{GYcgay(Do@j50{&(Fj( z)PYN-BvbK@^L3qyv;5nP4;|0nSAd}py2x(X*B$=dJ@VjjhmHdrqm_Z#_hfmd#{bNQ zmccsdb_!Bh4h%PpCmzU~xCPdssdkXV60zdvsekk$__Qv54gR0~;=({PUw^f4A&!%^i}(Rgg>nRaM}yik7R z_nk|huU$ll;z-xot%~1&BX2&lx$2lAAn_T-q0`1>U>Uk!vFLZo5bM`cPWoeW&)IWu zQq^sme^XmUXtrWOd3NUoBO#62(m87^y4fdRFzjwM7>KCJ{Og_2%&vb!(#c>H$ z56&J}x>Yr~PH<^3GhwpV4ebNRGRS|T#{{n^n`E+yBD<#j`081_BYk5JP@G{|0={Q@ zTc?Kt`ko^{1mwv>!QsLo%Y#H!gwQs%5z_rDZE%Jg25CaD69$(LA<-naGb}v8n(4>k z$PjRL)Pu<=>MTZ%5McRhO*_M0;wDg*?fwEmB;*_pkcfB^$6K7wqb}vy*OjCcLwhgk z+ot|je*EF2U5v^6V>G00mn_k9m%dB<5QG1hcvg+?_YBu@WRlO4fk1?Qd2&Vi^4z+6 zaVx}+(b*LsNIcD^hJGi+;6-Mq&m9RaEU09kGFYq9z(Cz2^5!ucE>o1u!-2O%a~h|s zSVj%FhD=dbnhHaG4HgPzqPS2I1hm3sTZ*jf`XBx4m&m)aw!Hnq|PK+cd$|{rXkvx9-R#Y4XFW zZo%Quy{$Z;_G5yi9rGLo+rBM}dYZp=WF$L!?>wvHws!XzV$dA;+wy^6tq`cw%oYFG z5Z98>kaIyi{$WtX@{eFx=ZJ`ooRB{3Rld6BqO^AzQ&?~y-L5B#)9s`_#osf-mj1Q5 z3m7gHB)#$_Rsx%vk+~-F!zFaDn+>SDxDDOyE?2inP1h$%*8PLZ zw@Uom(P!CIcSQB+<@K zCAo5yatq_>sN3LLpn|3TcB}xJB+=^e1k>+r2<6BDjFJCLD39iQK@wxO+hkZv7_6gB z^YH_*Uwj50VVw2?X6K0gao{1}kw{Mj)tLh;Ax9B(^Vrt#^YaYCn8OWLyAUde)h-I6YSS!f%)`cOGqasLT7QRI~1em2q6d3XJJ7EFyk1HG4d|H zHz0B)g0j|3N$N$Ly5Qie)mQ&z`G?SGiu=Unk3c?e@f^y}`Epkhh@9vG(9-1+q{KO; zQB4}X<8su_Ev61&dQ{jvwR56&U+h{s$3}0N)1!WJ(c_Qpr@Zb%evOQFGtP1X8+oEjF5lx}%QuobQM?mMNA))t$K>&wAV50l+<~VFYlVr0|lXv}hwS0>C2t&5V~?-QAJk2f1@#uv{qlimV$Y zJgs3}n9=1?*v6+O$MT){HESWO7t5aC5VqF7SgEO@+fF!crgU-G~ zDU4qS($8%Z>xTl35{Wh7_iD%$`diL(U86qkD$`s#!E;1eWpAcS^1XQEIKt|GT1U!N z($!apn#i&Ng}$h8OLIk=n&AwrrDmqHP0Dw1e3L!}p$Y-Ipt|2pMsN%O+HugY=lzFS z0p5Zmv;Ldc>nZ!BUeG0}PL}v3zJig-n`CZ9Byp-Iaai$dnKzpU3?!g;j z*+DYig>*GR(bNZ#z7=_=)x>g9K|ZS17pIh!=5^OCtxac+#tuI0p600j41pM7`7}^i zH|}KpFKk-Cm7jg(zTjpCWOpH6A7E6nhKot7wBw+&?w@6TEi=%3@5*~q>f_EE;A34i zMyxw(xCeW2fi?btw`5-3PHZp#xVoNx*_p+dB`}-r&=ickG+bD_BIus|$B|X3pIgLH zU@!XDvvS8Vb*iO*M&mA`br$wJl@ZU@eOR_}ME)Gs?f1-TCH8efma|{%)#N=~g-HXE zV%f$m#%xo6^oK{xKsgvTE?lbnIv6Rd(YlFXmz&Ke;F4;YO2A`3YQT&?pE31B<9^V& zYMlxTE#g{WF)p;T3k+Hm7T3y?XJPY*GMIXOm3jr=9i>PF+6lhTGiDq^*?hP<9hrn= zh|12n3I#1V-_CIzB#k22Nw<{>Kit(>9uIj~HTZ7c+gds>eeh|ou%tXCB4FRk!7AUo z+>vB-d#X>#%W_UNz|mczQxBVwQFEuTr1ZAmS4sy7>-Ob#X68HxY|{8O+y+Mu{=zg3 z=QXlC&V9Iv-?SHaCpOGAEq#FTYTa)JO1CuGLJMR_sDQ@KiRe7CC1oW z5Jj%8@!fjdUgXxU4+Hh?a$j`g@hl#iTbpVOPe&fDm0r9Q#vx1CsmW<^`V4(9EI@{P zgLKwEh2qBKr~y!TRl?v?Jg^klg%JwBq>$zrwn284ByRujRdw;^R#%YN0h1ZHg>%^! z&bZaIlD)t+3^slef8>K1RD%4$>v`e2+di;C;4cOCsd^5TKrujtAe3ac8?8fAjnUI1 zpQ;|iZqiIAz2!q?mhiey6AfNJDIemU7da`yAc7IUejX`1PT!eVt>}r? z$5{!L%kGf1JRsDbGp0ezvAX8lJFQW2UEWI99Pr+Y&g3+nUJ@}NCgQv}aJHFKAxOqm z>s5`lXq8&Oo8>3yv=zNdYxK9^VLYQ(}Z z;%-<&Pum!|O-QgLa~JEw74G(f_1htVAqgPa z(mK;(gCRgx8FY%qre`9Sz3}7R6Yv(qOD&uZqeoQf6AzB^kqvLb%3Lz z6=%;S)SD$zgeM>2t0W(C6)>~tYSj7!NJY(Eo}t2MaSftIOhyUi6_#sZ&76QOo^?JW z&9BOpe}T)fkE~sPSLYQ4Q#6E%1s=_dnTOtEAVs`KkPtr${m8t=1I@n*!zNO7meSR@ z_LCis)_Ttcd`lJpTS-&#gtZ4J-dw8VGXj&B2{V?Fio3jCs3!EJ%#vT|12|WbuP)*( z;YpiCe%5=s{HK*A`eP!Zu*#@H&xQV=U5-cW1yH$T4sS1WKIZ+vfdVV9MxCL}-y`N0XEgz8 zL#O&y8F*2sn> z&GBmos=g=B8}>VfYuazmtXyLETXZkc9(}R>h%a8OYy0Hh2*9MFf3!Q%AIqJ$&|W7$ zxs4+}2Ye<<5`wLncnUDs+IlTequ&kiQHDVszc-|Q$A9HI zb$%)^2gyP(?CDL+OECyeAQ7xH5MBX$dM`VRhKm=91^T(_F~09B%#Bt?wT$oR z|3GVZBN!%)bsS>SP0)w-L?bc8LGMST@8VuN+dMfCb&I$R8#<^E0!&B*X|;5x>mF+V zDCcK7HVYJ(!Y`r!3PWKfeinb84kz--z#jhN-MbVdSsRhxW48R|H8K!v?OiSSc7x?s zjUwb1POfMXBJ_>;yG!WccHQ72oH3>l8f3 zeQQzSBFj0&Tf3DJc}n$y0YV7OHsq8jfpjqOwFt&lbZlXJr zgtJYnGp>N`gR5a%8nKI;7U7QIYHpYp>S@T<$({~*qM|*>z(2gW;V|N9zcN}~s=ubU?cf|i3cJhI_7bPLI!T>p?KhKGC)y^Ne)n^I3>cOA zeZNT?PWZVS)**H(HhX;wqgPglJY8#Go#@Gg_SCaK66h}IdSMFfmZY3Mmy|9*Tq0?v zTWjZU9BltNCcL~K`(-I_IoT|UKpmV2A2UtRO6f&&Y=TzYQ>itsG}mAt(Q}-If2ATq z%WMrCX%bI5V^w8FEuol#;|2Z-=~Elii7{N&en=#DfFmwff44O%E1Y$k5&n9a_D`Zf zhJ;3&0KL@{e88I5zwZrpTVJp?B+<+Z0pO4WLxWSrCXFg*>lD3U4uhVx1S6JW( zYFg1UBuFfhFq`3sMw{9)9+!{!8IaUvNL&2sa947yxdpq)H@HQ9EtpZ7K_#Z?M+BoV z$^${tNqFP8Zu~ngky*TXK(%QB{1_R%wb#SIms?nW+dy>GD}|4~z|T8UZpL7s@9E4Q z{zd8cDL9GHhb|ak7zN)AmA`AA*b8gYV8``VA#mwtbEC#>rsyvFb;V=$ZguFd{e39= zf*L$WcDN+^?@7xQ=GIGAjqyHt7m6!~!ZM@MMA+l@H;76ChO%|A3^{nw6VaEGbrpt% zzlLol%2RD=)tn|Rmj2}QV`kSG6#Oe1S_TIpEDRelP>1h}8NdO;*+}7FX$VsmpvkN6 z`_0H36s*@a?}KZ3&+U!tK^Q#?}QZi@;#MLAXsAq5+Gw|8eNU&&AW-lBiS$Lycy`T7*OmtABSL&YTl_-lz$_>wdvU(N9-?aui zz8I`UfO91Ki^4TYRWaK{L))U(^IbNdK%|Qr+V?ecURq}e>;QutkX$@q$YfR%xCSxV zXWc}{FDm>BJgVKWa$efHBJ)BTf>hTJNo+73<0*~`pRx+S_GY!GM!h0BU`K~(;3Gvj zWc42YkQw2$hh$Nt7C}VY`WyJ;QrT~g2UnN$ClS_}}RuABBov(m|ZE!in$;Lg85TSklWq$bV+_mA+u(dqGf7K+f`w3{KX<4S; zqgh?Vq+UtiqggqQu=K5Q{b^wQbtB`pCPU7v1Cxxp!_Mg6WO%4c++23=z&0SY`CeoH z@^fot^$Tbmt66)`HIu%d$%*G=FyJY3D>CEJKa`;cPWO9`W2c%GY%km?0wM$+d1`Nj z$W?)fLavED0g#F^P_tW!d&qTjzR+5;RdnP#)iX*8LG^?Na|0XgA!XIyAw$b&Heb=X zGC?;6c4IA*x2G3o{Ks?7Vc&tE81FG+g9B}=m49CaWdPnSYP)`;Q?QE5>$`Ta?4mf- z_uNe#sgW$KPLF=YgOMz*AyLD}@BQ~^uFS=@CL!o=U0!A|D+QatF*4tcZ123yS8Ok6 z0*8#Tkx?CH8y(Lo_qe&;%WR)oubKwZWiu{O28z4VkksZ-(@SRw57igp>=k#XuLl^y zmjYf(#B|-RN2z19X`96jtzlIGP_5j^3yLWuZKmA}CY%)J3fhV3U?-gVQ{)LQ``HVO zXUh||pR#5G?A|dHUPAhS>Swly#teZOs$1~jiz#nzz5lKxfd8O=mue_zP3Ds%=24Tm zByy!~LMdZshfpDhRsLnOrDSDQS88P+dva08F?cdtc>0rHd6Dq$=vVFnSDmPwtX1q) zuQ=X0A{LP;O_wH69d&uPQ^T-)G8v;-#)WYjXqQvqT2h1-t4of$$R+9L0zrpPUG0hAt%$PKQV~3WeW0v1MJl zsy`P&CR6fT%3)nJR|rN(G;XsQTsW=3!a$f$K>&K-r6g=W^cEGCc9>UP@X5alUk;AC zyIJU`F0r|Lgq-XB7#%IX^X!Z*)kFnEfpOG?)<2Il7`P^RHiRv4dE85IDw<#@jm6R>cLr|QL#N9j^69ZDxez87}SQv z{XX!97W+ME$a4Y)$Sxf%h(F_^NAmeT_ID|OoR^R3P(uy66t5E%Ks7VQ&$g=!&=D(XsgoZwK* zln)#4jQR?Hq?U^EF1GBYm;;ndU&sFjXY?(o)3=%-gSAJ7RRtVLBn9y8*=>BJ*aSz2 z=vHe^lc;zxjxM#1%@+J=9M&x;wrn41iGFnYoQ{9(u|<_Qn~ubgSvbjqu+*z<({7CZ%jUQ^C5gt{s14Pov2?fpl0 z)4j4J%l13fL1<$H_FY+w59dwd(@LfAmsT}~S*>fjrckM$Kr!dgg@psOLQ3GmU6|*L zW$xZcf~yg=g1_gMzbA9(O`HoT{`up-89%#RbGl*T;}gj$Qn+4;>$Ynw0@h;B59;s}F5>7TeesajnnboG=Ihq9KAHrSXCxHsMSL zN1c(04d@t&F^M=X?2^YDIB1?0I(rjKO3*27=(u1K7J@Qalq5-|T$T0CRmbx}qUsg* z%W!fib>~TYgh>@089&FIorO>dU>zFxiganfmL`v{WZXYX@YvO93Q!RK1nnaFhH+yd z4e&$O{0aZUP&zvgnozn(ML&?pfm!R|e@AY6B2u`*bd$x^rh&aG$8AQ40ube~g^ky2 z-%Q;nALM^S&#)G1SCXRFIYu*LT7)@a`+B{*s&B?;ziQ67e6A3lFZY!CLaW|Z&_rX) z@uej%gVN;Dc7G}OdLezH3|;KHb=^+3dhsG3W-1m7cx*0sIGjaAhLC}sIhRHvFn+f& zYF$mQwP_!wF=~l63KRj~kkUPZ(3~K|^6~lEqxcJ=p(8c1;=q1^?7KsaOkRx>+CbJe zuK~H{-((!pYpV0tQZIJ~I&$SGU!d_koR%BQM1DML44&U z-7d+BCIN&)mo?>sTZR3z5?C4tLj*Z?)sLhmLYpKo&E1#6l;0@NM+j?5J#-4iKqgC8 z!z_dkGOgqJ+tdyB70dVdE+b7@-A{k;ALY8fN3{ru%ZRe}ixS_7V3~zKH4nw&zF9iW zT8{6Ds1hjj@t3LiiAhaMwj{!5$L;osJNJL;$rFOF!mfQ(%jO+zj{#z6!q#g zYRRD|(~v=zB}XhsG9`!Nnwl`jl*2A$pFYw`t|x&B4x(!QR=?*RYz4;^&Sp{wsKEQg z+R(t|5UYp}K`8WN0eJ65({!@+wGZn19tbR7{+j~p`FCNn(KTW@WPQzB8JUON(NTi#(fC>Fk zpntB$sLrz^r$FU7KY?& zULN23Y|;#WdpBF2ueoH*y@u!!u7Jdz<5)LAOS;k~PDxAf->m4zHdpzGoxBQMVVD4A zctq05Rxr>Y&99;P#(ozYi)^|%c^13i@U z9zUOYw;^^8Aa?4QTZ(h=7em1w+fW#Xd6J5n4>}0 zWUO4J9-#tAv(iw*yVI?uh9>t2>~cCNFCaOd zzjDPpIPFY5Hh45Yd9k{sz@vviUlYOi==U*aN}T{t9q|_t*N_A=~%ZbdLob%zk z%iE<@|DAn}7EQpFik)u}Gl~)T?2{qgT{SX^w-QzNIG=8I>i{irsY8cBWno7j0~ySa4k)e;Cp~D#{QRc4(@9kBj?pVlJf@1@E_4TD*ZS{u2DL|dJB8?*FzCQt(9dEDoUz3;Hjx(VU<`uDH6d&hVl z(NBPu8H=0Du_9y$=tzUFV&HI!;fHm7!{>!^!2*rpBDPIe5uCN z+)PV)z8o+oOCyORY~*{+)ab<$TH8@$^{t%O~p}DIJVlTrg3u&7kNC+G;lP z3Y0dCP910>6eeZ@vjJF%<>@Q@6keXyZ;;H{qvp7cAag`8Nh^DDgVb1K^7Ez5p^XpBSzBQ2kyd;{rGYNhwXUyEw z_u2`s3o?d5+M?7);W~gMxmo1K$0_dn8$xGh^&f%4M(+r){OEZd8oEP%``#E zL;fU%J?_qzP-bvyR&Wy-asCwh#G@1MZI-^L?XDSg%ulW$Y&aO#!Y(EDmpoNXbUZw? z5b8Z&_GikLKQQ6{6#$Q>ss)tOiSMOfM@W3XXD2t0gePc=io#Jqp#E%USpw6RlOF3w zLh`+e8XanuNyHcgLf7O%5vyq-(817$2lLH#JH`mstSs8~%N0Smc@xG>=os%fSDGF*?CZ*Bn&gan1( znU@^{-}HJT61NZ3NDaL})@#bsp#lriDs)4|?4q9I#nTBu4No60=y18hGVQ%e_prxq z@Qdf4T_{eq7Fqa$J3=Yh7Lsw1SB+P~DX~>5BhzD8J6$QKN_+N}BG+lU&f@j0DZohn z_nZ4q5kb}03Tvd&b$yOgxeV4YT0Si-w6Jb41=URZgaqEpBmRHRu*M~U%O;U8)1WHc z->2(s*a^eq?P0L+B-rhi1XGDk?SStsM9gF#>Rd?I#5{$hD3(a#@ExbWs$CdhWM5^t zET?jLfwE*z6sNjNj)d+>)Q`|6gCd}Kf%%HFA`}tP8hEjZ$t8MA=$v@eCaB=Fq`gy! z-1lF#Pqp2N!y*t&^SbZ=l-<6o!}=M~B=j<-yh;Mn?Ad?UY=;?RxTi3MV?hp?in!g= z>(F?SO(E-o+u+TbGPF~v6=7}TG|k)KxTj9t1}2%zH#Eg4%v-t?Od{?1+A7~`+P5vT zitPoZCOn3Y=)DcA$+95-OPIbUdKZP;RFTj|2OlWK5Ta1*HmjO4ltm3JR!V+9)$09U zsUtsIV{yY{jd7`*B&PB}y1uwoHy{hxmgktL-&Ah!NjlAl#tC>3+spd2u1M7Ih&I&_ zJ^AaOR$pKC#Moo|h*T5QU5qSwwkShRV&oXl%-Eb>air%mOEZZe3MS(X75$B|araQmzD4Xt(*V<6pRHD8g=|o$}Y0LEuB}3%Dpp(%!o~ zvsQ?lHNSf-i-s7{WrMlqqHp8K_Hm(U_!AsX-z`!G(D+mYR1Co;##nkxhtoIaZ!BW0 z=3V!HO21lKqL0A}h#b+C;?d&56tW%zMc*(uV)HW`*kl>d%xVm~IP_d2?WxkX-PV+L z)gnE_9VcQq4s4JipD8BSAl6pA+($8@*0Jnr8CN`-7UGFb6xBpRj%0A(gDGx71LZ6B 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 Xe;rg=qXwD{2YV^Xs>#$!efj=BAxt7& delta 38421 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 diff --git a/images/hybrid_engine.png b/images/hybrid_engine.png index f501c04eb61c7764e405c53c0bf4f84b06540e67..6b2fa17ae031d59f7360d31ac67098093a34d818 100755 GIT binary patch literal 101738 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�Ox_ohgJhGXLaSxXr&A1^{S;L?Vv_oO}FDB&argOo>ZGu!4Xm|%$6U0 zt!?^lE}*Tz27P+@?}vh*_8SEuYc1B}A)#gD>6wS;A0uJnTZiP&DK)n=o|1$Trb_&Y zHD|G^Oq>YvR?IbV1wxitj)l!!oXSJGI_bZJpTeC0XEh_ygxZ6C!bSi+n8u}~R+R#c zU+={s=bCs`+47EE-Z)J!ApLcJ#1EKH0RQ>s$2(IN$eC|@-4Z6NRFeRuB)p2VWCC-# zZR!N#NLb{_)0p&Ha4a^DLr0lh!`J*<>f7jlSO8VsiX804{`@)T;Ih8FbQ5ydaFLsr zS5S%51ya)zHf$O05ZK1}89M*`P9;-I*4?J1Z-w*00W*+GaA$1DO293y(bcvar! z-5n#}qCdid;!XWet0y+mo6TU9+rIDgdFfG)1mz?^sqCotg2@F-T4E$;gX*JoeyB~8 zu9a#m(2@!NvrIpN@B0m#hL>h59ENq8rPHkw?FQoU+2)5bNz-}bbs0mU7Swy9k6qd6 zhPNhU#)LByoz4-J;lJ8I^}ex4lsZJ=jnR~+=zLdwRMmtbguDL(#%$a6hlSTeB83rX zd-3@N>k$1+=3NmX+9$`}^fmwn@LvF*Kw!V{Ghd07LuHcSu|wp^;;=ArUFDFw1eh|8 z-ErFETLUro=izv+E!Ior;cM2ZRKTm|nlG;ojyrCCFk!-S`h$j$3ue!*4W>=Y1xuIe zRc%wDzq{{#i}05Ry?g(g*JNiaZj^rwy-i<2ClSBmK{@hJlNoaauZ{mL$ebDQ;`n<@ z-S>Q5ZpPcNRJWQXR7cI!T>Y2OBfN8Kj(-sylp%HSn(r&GEN)n-{(J7++Fjb{gr2Xjn8Dib4z%l8D79~?rL2PN7x^jr<>l(*#0wV(Pdu@x5nh-J7A>j` zrc9{~=FeY~7ry+mUQWLjbyv`(%V*%cn)rPm2c6UPq*uQ~+C!Y*Q!u0lW=T1kFjO85 z7Z?*rFpyxNEihn=t}Wtfi#U9Km^IJu-rZHz)a3?_@QQln$25NJgt4o0^Fu^f$B5;F!;77N5$B-9A#zadnu=W^W7366`k4LHf>Fp~G;=hYoU~SUV z41LUS+>gY5p?7V+pL{+@|C=PcOO&+oB{$!}kndx0@5{GV3Fk}YTTcQq>+)3K|HwyG-bl+znW0|uly>n4ApTCt+mJ@UxMZl8TLk68wv z+<^xOd&3JG-2l?={75-ZAi=ujVK?*m~|)m9nrUyA^L1m z6Bh-v6)M{-bJW4*9z9C9;i@|a@MgHP&YJE{IH3#sXDe8V$?}j>so%o0eEDjGlWOx7{{{IJ(i{4nvR` zNWE&3u~BF=?M8jpwPskt<1pNSg5o{RcpgZ5Jr&g*JB8U0pmc}QpM9VH?mWy^uc8sH+MJFOsN>K!=$laL zw+rp#@alp1j2D_!ovsNPy=T?6YnmJVx^nTxTzA-EAG%Xd?ZB4eZrp^WtLKaTr7F7h zFPE!zQ>M)4#;XrFz&?qHy2A^H!V9;77l!BK>SB+Z>h*bXhs2y=B z?2)bF=BrXxH*&0wEJ1O=z;A=^0aMBXFQf+d9xvXe_t8ERZpc>5x_U!`Dp%C;X_{pK<`!UQKI8~8<+NC=HCD3INGh!14p>6V; zV4#gKfDrx<+MN2c;izibrOrDZf&EQw7e(+`N#K$%SKnj5-3^WON73x!z zXeLJ={cZAADKkp>dxQM{6J20@CH!jsH{uat|3ZI8k_u4Ar}ZUXxDe{?(xnIwz1_e6 zU4jxXoQ4om#SMU2`m?NihT|x!ngB(u|83fnHVpA-Wj0W>?Bk8uSYa9n6Qm+xA!aWnRYZ zN9UnckUOL3cSP5KMj&Rr7)Cu#A8Q9@}rDp(QGi?Y!eUXiQ;sVHsT&(!oFl}wV05!`B(QXP2) z&Q_~0V`64GH7ezbOTzJKbyR2BOwQHbk;(n#cS@4MyTW-;Ttw#M(6%kJkLz`Q#z*1C z*9$8_h`cDz5zg+#IF?Agz4|!Z6}Rl`pe|nJsean#W;*djX@_ytk9%w^?J)katni=+ z?#qU5vwm5XQTA(qs%9~p42z31Y%l1BU{S#pl1sVgej!Trs)x%s$_3G+QtcX-_ z&wayG z?~7@ZTA6zQrIE94nL7hb1AA+uvTiSk*WWA>-CY6)dAbm@XrwbrG$Swub3JD9G37bp zxtN* z6|A0!7mBQ#0_)?_JdAGl{;a`-@{&v zPH-r*miuY875zZm4v;1ObpAt3uTU^%*_{7+d1=lgZpN(CdHfxvgedWqfrBbB~R!b2`da^uS!=NjN zTMnk2VQWu@Z{33~rQ{pU0`V)W-UDfuii4YH3m^~CU|w#zPWQAT#?=R5c^+f^L+M`$ zr|ef*=Y+Y4>8|RR)3qgM;lB_5hfy4I&N56*(K{)3)qEoJLP$__#_lettsoxqldEwx zeO{HYLc%0*S_iE61TT1JPf|@SOC{H{B%!TbDNZf^ps&lW+5xXfU#SvTll~BUUe_=o z4*3Oz*CML0p10HSCGzHTFPXd@Rr#HDQr z+P|F5cH>3hF4S)=9c!(BxxT6@&FWW%flCHIskeY zM$UycG-0K!j9LXb5_<_ILuiMGhcxWZOlkH=U~zn zgu<|68YA)Mgz-(mAkQgTUhBR&-dVRw%xetKCT9?wQykyCSH^hFYMH*E2|vh zg*8mhsE@1Th00#YSsTlO`q2F0IASe=7v@*`tt^S2U|@Y>z-XnVG#YKdEd|#4Kmmr;Ecp2Fu`YUZ2^b#zDhM9? zqrM&mkegLiqmuFmZq02>yWOhD#0y_AMLD_fV5^DksWDxh|*@HTUS?d@Ok7%{9R^kwK^RB zeHY{V$5XE82%IQ$4f*$-34>nwOC$YMPfevWXQ$kiOYr{-7j%By1x3%fp!_M9sT!fU zoI76m1B1E0x}d{&B7cU;f0pf7CY0hB401s!_St9g^BGf4 z-0104R5ure?wQ(aT~PL)Fnq}~2n4FX2y)#h$McFm844MAm#5S7=ND18rRl|sJET{wDrM_XQB)^1MbyPQFlgpvbL0)-bnHw9}$rz&eaTvKt&UVa5yRXqh=0*AJXbwK$+ z9)l}wC;J2ypQJI|w1gjmB>)Cg9|?pQXpCmWf3&i2djlkWU*ELBG0?j4BGF648yffy zKjUqGZOx5F99eprAwT`dm-^sR%qK-|qIpa&V>*u!XCuz;}B*cHA^}@%0!;g!tAa_z&HW>Qn@iu_%e1E zVcbXG5!isjz7@BDnVZthUqjuPqU>c>_{JEK24eBS)My4<`(xKDvKNjYqkONT3yfL& z5AE@#Og(Ua9*an@@J{hGWhR}EG^@;6l6Nl8<@!HLvM^t%9OVz^cQ9KyeI!l&Xt| zOk)hg@XbY7JaLh#zUKANHoeTHw2df72e!H#Lp?l^0*mfS+HV144PaHQ1S!(-D=Q?n zO8b!12?E{C4*?Z-z|8xqPNb`dfcHbqrLdP(HTYFg6rX`+;&P-{)rmZ7SwVZDV^B*z zeZIoDS$GN^J4I=y=tz}zEp@xP0}C?owjw{^@dK4LTV4I|%uHtBw}YTV57JYcMhSOT zr>5i51hibQ+Ge0F27a}L>oPxl6^w$jw|pUp)^OG^1#ABuq1S!r&=)d6@K)qsVbnJD z#=(8G|7S2BjAC{&Rf+%K(8j&sB|m`57H_AGna!@D?jvaA$MW5j9uE?4TZh0;evG38 zB!SwGR#flT2N16>eQ!ni_M)8cVxA(41e(Nv)kTvYHggBHUE!c7G_VIeeiG(SqB4tr zN@<(bjkLj2pp|VRw>|j&4Q6YTvX0Y?(M04uPfKRsP&a=Kb>+?KNVjuK3)|cDa~YFd?1$_HWhxx zM27k|De*!<%Y0D2TIPl7<2*m44B7>-c&fs&!HYc1Q@K<>h6~f>A%O$~t;K-Rmcp62 zDZQz6Yz#1Qp4%3_|}EO5w#I#FWG=46IW1EptoC!b!J0$!yF=b`6!>4XVkT&UY|q^e~EF4ZZ}z z?@HYM2VD$%D^xd_u?Ax!RBo8|M=<7J@-5**d5!0RPY=oqWgHJ<99~jWbASeZucI`} zi~!=5mD_Zz*N7_~zxj)&7k8Jv@G4csAeD5EKs}_+#c+esXWGY6;%cW+%l^O09!@wT#w@2SAkDiRmbQ; zD@3U-A*!(smRddg7(hMwRCLS3xYMgUdF;5QQQQGx<-USE5Ph&jb!H&Efs4n$@d2ds zr=_*Ee|iWz;Y$9bODQfX`H$kQSGVSdb3JIxUUes>_NaQLj=qY&zw2A}zAn)lf~CEj z_PmI8`~RTR0hph_;|sLmx6w7Yn2p`$0TtR*>Lic$^*UI5d@ShhkQ+KW_{AXLryFd1aQ5M-CHU<2VVOlivgbt;2w|ONElzzO7%EecT-PBR4_3y~^B_@}8p~3lq27=*+Q!)&|L& zkX;QUz5#|M;o~|Ol?LWoe%3(dZOo6ya;CwHfx?PG2o{A}z9Ov>tzwONnJ#y8li46% zv5LE)lDjb_g;VDsuzX0jJo=7<{C|qthw^NDDRzFr!4jqLkLh16RwgByamEgeLuKrM9mUc1|!DAe0D-S$5TZaeB zA4>;+Spi0F;SG=5U8V=CRyAWN<;XA|#V0)Jax1Me5JiyrFPA=jDz;bP_B8!dFWl&) z7V}(p=FvaQ&|tCUb!V4A3~l<)=s%FctorzX zHvK$<^$D2gVeX4g8S+dQBOj@h{8EQ`XpAn+jtinYr~6TPGy?Q&Pxsv=>!GA6wpTqgy6Z-9mS*x$w_x=Nf zlQ>U`$CH>73^c@m+J69_f0)(onY69z`2M*(K{Hxudxv&49vvF=tEakLMWAqTSjva| z2#lso&(jKSGRxlV%nw$1FgkBYBlfK@M*p(Yw{(H*6?dLy$p1{fb>8b@=;Q#*yP+i5 zqq%1&``PI4>7x-)e3l;cJB-iql=FDv9|kU#!=tKkUqza2-N%Uyl~S%Ucws;~3lThS z6pqD5jF%W5uu34oK&vp|34jpNrHnSsY&4qg!UH$``t?hfmX@Y__3D)>FV|!%ecZBT zoY`F&tX{o3H+}ka9vq%(G^%!~HmYx#jXq7sDo?{QZg}Hd4KyFeB7caNsuy50`@}RUd8)8a+t1=i_X(KoV9b^`xmjHDuRO#-PnBOlp8phCDzEW|6ZdQJNVhV@b=k-L#KW$OS!FS$ zyfsu-R@3E~e2k^r8SBe3$&D_p%#<8M*i&&jILyB1>-quGNIJP?IhQ&iTYKHZp7_Fu2*9ZuU)wZ#-Uww=kB5#c6t7+&Tmw;=9CZ}Iw! zo6X(ycr+ew!#DK5C4Z|9^&0l$ z@VTAfbA2LY2ya^|&aCX6z>E4Sc;XtM!7mdK9)u@_Q5zOMZ=(I3g08lyZ@HOS-!fYt zC$roQ;y91n%Uy0tgNcYW=tZ>P>6kn-k>XeOWoL1iZh6G14&V!yBdlM5jYj&)lfpIy zip!LhM`5fimX_&F#nT#ll|8PMKPi>V&X$M&q2p3#FAxJPLN%6GAz1>*&*Yt zn6bjjEWfHSeU~vCCBHg+DqV4Fzq$kpCU~o*ODS!IReTC7CM_kiDps>ngBXNqNqsP8 zpC9ywU<@rwdYVN&8^-)ZGCg-*_5}4zRkR@x)DLV0jqrRZ}U?xEiB2` z@eIDh>uUpdKKCCk{W1gg|H9#ba3)J=6n?q!DyaPw%pJ`N%15l8Fd&^vI^!F8i(?qU z#}jOA-vLqB5va9&2Si~TzA>V*j-`E!$4&0qpgdN1@*rUZ18O|l2-Mhkxd@vFnN2B7 zL$|!gbuh&1%j&VAuIS_bno7>-RWrHA)2{yr4Gh4uDpvWIdqWT%$X;8vjzVWu$Pc)@ ze}s%H&+Ht*0_nOCDk{$DXXDa(WwSG zqS@3}T%T+YSH@kxOR7I}tE*?YrJ`3U)r)SZiwhueJmZ`P8w^%awoBkI|AUL=UwQ&f zW5Bl!@SF%B^>O6!A}jiU|AJmF!q^FA{fy|KAy}Im2hE&@quQ(bF!@#AB&BaD(XHCK z(ow&re$M(hA2(^LU02b^Nf*6Hx44v+Xim!LMShB?%Lma%p+rNC`Y;LB@ErA3BEny(uc10)pR{QsgRgUa- z+ih2b&`~^Z-n`Vq4?irLQ5*C0ie7xmDW{YS88W1ZF(U7}EvUKo-g{S1nKDH@LbNEA z8#;7o(f<4I&!fXrs}V-3Crp?inic(;QX95*Y;D+doHh<l-)G3G%CmH_fo1_<)o5oOF+j8NtC3>l$jApQoUwuK4z#=a*?w`%o8SKQj?ItyL1 zvU9MX%l9X!$uxvX*r|fW>{W@zpf!N%Luc{;{DO@d7-1ENN!P{*YbQzpUrTdk+Y_B4h>fA9X>Qm&>9hK4QRBpj4 zSF@nX)y`j;s-3$!S3CKQ)VzsfYM+^a^CDDsf7gYJsLN}nx;1ba;~+=?ceZH%t-c_*!Rh)>%TpH(;@U*7Pu z#b424oF3!m76Y%N2jhPY^>vIk3BCnjz}l&`U-3Y-ebG~9@ZiD45;|6`T9tj_g%{Mm zL|>h6xZ#Gud+oK?;Ns%q0VO3RJ<+?d+TnHF1L$ZOVdhj-RV`YvV#OT3KmX*DPd@$n z*S|gy_t~fn!brNZva*&fT2-u|h-Rh46Qo3&N-wVX+&I9HwsGVp%tfAI!Jvo3Ag_i| z?u?TmQ0@kv8N&3=!rzTpa+ugNW*-OP`(ytF!taWoUC_0`R7|^2&;j$02=^ZP8!*;a zXaG|Qth@+!5g%9Gml&}}^tyL4*S;N5CYS>WqrgFC$qE!G8 zHdbZk?y2L8cg%DKpL-pYj0|M z+khvn$Q>GajkiHrX`rG(%>O|%5}Lc9)Q9;#1fiol_PUjBSL}8{sjq~z3J3bOPYM4% z`kUyl65q#!7VRpZ5T%%V*x(QVO@M;=+qN`?dvK3&l@*Icvfs8OT#=-j#Ut_T}DbKYZs z914Zy%a_kZAo+l8TW`Pk;)}1Jc;bm~5LV+#ckHpp);{*wW2-qkq7@VI4k_^zQyABp zCKa|#!4TJ`k+wl(%9korOt3%9Pur_bLB!(f2}G-V}|L!s|e@W>IfO z7x>OOG;UeHAOw`wt%%HWCwJ_f>F{N+a1LsO=rtEq{Wz1m{<1At0NGR9_^^nXHyVsU|tG6rf_-{8$bkgCz%pv<6jTZ zL#dzXOqhcS^DWFnQCj`?KtEO2$LWMz1He6RLwL1s1Dt{x*gcNAlRj<|4%+gOrY^O^ z;5d{5TcnoH8p2&nAAAvRN;}UBFei{;U~|TR(SqnmN^M`NsAI>DC13vXmrL)z|9;U- z$<UiN`s`r%lsFe`}l6MepZb8s_iGa(# z@|CZoX3w5o_4@0tYkQ?uLmCPqiocEt=xYN(z91VY{g#q7MwW%y{u2H{7@r^T zQ!zS(ej9?v)-dT2_!Cp!(;^4c@}DALs*Ebk!ar4T{l>YkB4gm|Ylb(&kuHLOno~vT z>L%sI1L=K$VH{~bLYl>-c@t@B2Fe(}j~;(-X)p#Oe=ZE^lQTb&)t0A$Q1DYHh(w3g zzXB$h;nQpA!Kta*>+eZ~kWH>_7`^gJd-hQ1<0|N4Fr`qN7ftAzvP=(`e!8f(`nxlk z$-O-@V%LLQ-*5DG9gg6LM~9HyY9(~&AM!M=EHZ2k6urQYt~iIIft7?2i6T{trnqJI z+?Ses-!E!jewSAff5G(7IpWHAP{hig^1cF^9ZSCd7qcK=p71mIEf519Z;1d>5VrDd z41A3tgxa1a($r_Ed$P89eI$VNz~4UDtB)IjQh43aCV#xwluk3NnxbFQITJZ)`;^!9 zL)(^LuZYjzu)`{!Xg~v?yhr?ej@|8qeGvEA=zro{lX^yoK418oPPp@l>u%fxibHu% zAi==qjsfd*L}*fK`~C>P?z`_^y7ksum;Cde|4jYykAK|m=%bJR8e6cAW1CeELkhH` zXC+eTA)%T>%ewEg6c(>OEMv^ovu4eD=s*AY&mG_S&UduxU&EpG>PH@VL=y;fcX>g? z^9jB^&7*HnFa&PUOtg@Ao}s}6PolCe1@FBH4Y=443B&qHcg3`wxCZRs!jvIh;aJ z2)#e!=MHE>X{3u)pMT^QUbbiYfvwX)`bV585jW7QX5t}};49}qvk&>`l7Uo&hPBef z?~*N8g`-aG-8Wza_YnvQSJD=DitN;F1b_kL=JSQ{;3@PO4kCZjOP7 zV;~~?fkH2xC(?hm4EiWWxh2_zWU z+%e$kgF=cHq%`i6Dn9hkLp!hyO6H>Ho_p>XgpqSPbm-8ZZ-I4G53x!axP>X9u~4CD_{jiXD#DGh{nzqE{q6^4FQJ44Uu-CX)2HE&;&Iw z=>0*ymV-45WNgwbJ3-Mf7@OJy9%ir}gOMvSkP$)7lDINj&X_0kumh zF<>#hUeN3XH9_u&{h&qB(B7`|OOLptKkDu}j22mN6*EFWpw}o-yaD7%OHnX=!0#L= z@ndLn3DHT3F6CMdX_XUaS9Vwq<+qC`HA_C{Lx)0)xHSVSJ$~tGR$b=ae(%Tb<(-GQ z@{;LX0<_5Go+(PFe-sU*M6YUxV$y>3+V;aFHoIRFK$0z3g;lK@t@;z;O*AtELE{qi zV^IbVU{KpUBC^l}pMDU-%{p%G+_{do0?e=5XPgAgxV?6lKPj+s}#>halUpShVc zXS&Zn|J=Rx)>~e;K$Vr1Zq6L2ZRJX>p4b3I`I<*spVP&)0=)eMzfx7d-Uj0#I07vM;&!kM^-&#*ZIQ_KRkuotj>a#G{Gf$f`pJ5 zm=?#X9tIXJTABrTrMB^i# zF=NI*e)-E^UV6(dw@l@Xh`amlyCsB(L5RO-JgWQ{jc>4a-=4%xd0P$2pD8b0@+~9| zhCyq9ueSs=JEVPX(j&Rh@ho}9bMu+tdn*KuliW6L2e!|SaD81LUfR~cM1ltk)jk2$ zKsEKDA&>rMazW2C{C>(2j(goC_ZiQg^@Y!>=H|m|@vl*{f~mr#s|8F!|6dx#UFars zD8E+O@+Y+}Abc3a&1Sbg^mv}*<~B`>h$&Cq)+~KvvEI^a)66H483xoQr8Jl-L1|!8 zGC1Awj0vgbKgQhSFYbWqQ{Ar6tLP{QS0FHj!a@c*to9QDFSuE}PC3KV;J^KaAI2|< zB?ejO@+jBaZHEvt%5BXnXx;fMg)nuoL9LHh8G^QRX@rn_iSZlm3B|Aqb?1S#-T(NE zn?Dt^c!=m#{6y_e2`23iB(xF|0iJ!{-Ra|gi9Q-777U4w$>f;O+lJ?c~ao1v@Gg7K+(UA!tqL+208Q~-j4Zq`VZDo$< zIc{#N_>*`x9}F1HiY~Gyd>nuL@g2D`H}k#keXq}NfBV~?^3KOGQISO(x>eCn0!a9f z;K0Fj$6Frmi6@?L+-&7Ue@m7uX%b6^9d?*Qpm7Htc%a*Fzx`a#o;{tmS}IgD(f30M5Ha;yavCus!>~o;2Z0nZY$upAX_%d$YX!Q3zOobuzF=#p0MIL!BK(o4%9yeQ1 zv$OAY)#G_Iv-G%mUDIeZJY-5){BYFLYSdq1xzS<3+NRN~2J~gT%Te~Qo4M>y>3x2D zZtBfnT*L9KGu-abYc!=n4Hmh7y2*|eiUx&Cv1XZJ#Uq210HeiqHT1WP zagS7e$hup3Ze}FHNIzGBlO`6JDe`~m;3oG$FvdNTzAG5<>8$jMGybdQm5;ybwkrC& zo3+$O8!~94*La(+pG@-A6b2Fjq$$j-X>T+moJjx?&2SdV>)OV4DdrPV0>&4C&UB!iKkO<&T6IW^R343fjAOZ^!kYen6xNT&$!-*H6>O{SaNk zhhP~>ovj8mq8TGb`EM;)A7(gFc(b#(8h)Go+UW5-npt|>yf*PBVK!e3SlbjWh-O8v zCC40dOos;_d@y~^Ip+-c^{;<@G4we+LV*|HC2-iPhgKiQk00+O6f^{_c5ryzaY)?@ z7Md{7YDi;($z_*a<~VrnCmj?|EDdYFDYjNlzVpsIe|+kxr@qNbNWe-+TnI6Gjko!R zSz52(ZAr{+ol&dkIAbm)5eXnED)U$*y$djRL2NOz?@$g|FQoXL8}7&d0?j5t!v zpYg@8X7creePbZ^a6UG4&A?%+-8;m5qx;ZZ$A*wnQqo{(9(*?vLX=;-4dj+6R;%o| z5VAIvZS|x^Z+J1Yk}~%w**4P*QPM?7QUcS>J+w`Jw`2@hyOeGNsgk2y-;V!uGcv!- z?lkzqV4sUWcgy#h;g$!(5e)*THyK4|X(kMox_@(f=a}Hx2#0A`!95fYkr=emYtq(*)WSejqrr2s1*>^{8O?~7=V=CWIVuJI z_Cw(K9sOJ--#G^UDF!F0E?v4f?)-P$cH@5Xlb^Uf_uSKO(ZG)f2`PnwW=#zg?xT-B za?Iko3opFTTWiaQ&XkZBMQWb)D57qrl;E*4QtIPGGuBTOo(Td07PoXM>))gc%su>S z+h66?l=5$8)7v(+B(_Z%1J*9Z<0O0(j~qF&1FIJrWOifa@VC8s^*Ri*+KxawWBlp_ zD-w=F;_jl0E^=?b{kC_MupyyCezf(`Rw$t_?=GPQ+S8>jj&melmp~#VfkT4Iwbx$j zxNXaC&obJFu9d#pvR+yL`0~pyKL?urlr3U8CK}YoiFR!z#M-K?Gv^-0w0Qi3=W zvWm_NA5_ReM1C2n8_EkGJ>lPT{bGZ2SGaw>@KNp;Of>A!3~OAVC@gDXt!Tg#5WCu) z4y3Pice)7(IM4Y=dL!J-t}lcvqY?as#_=c~6om;PR@rd|=i_MM$LdHKh}72Toxw{v z`f8L0asusUX$$Z(UPPo$r-b}2LoGzj5LGDurj(eGtmo|9*DvYiI*p{Qvhu+yah@JC?p-MNm${eRze+!M{krx=SV+5h%a*z&sG8US z$r5RZYHNT247pQT2d4pAIg4l?EMbOtWvx3WwWIqvYoLlbq-MY3LNLGk3$bVE?E`D? zxMXKnx@w6_ubje>T+*;f18eJ+OvB`p?=>-?HjoG)YvLttW6jxU#?PeUK@)5l1{~Yd zhg`uxLT3vD2_M>Sr9!XiTKc_xUkD$)h>_wdBTT>37!{leP#emwTl=X4N!-fqjgD%Q=0I-bnRX^Egn{7tp zZtXrVd}!6<_rL$W`@jGDKb)N#G-!}NZzZ~u03snn0?6XUIyOuqv3^hAM$I~W?ua9faDVv2AKZ>R?&!Z`^ekG;a7OGIZsPjsOE0}NhwWQcydPp`Lc)2y zu+3Y(eH`uaBV=xj5U*%2Ws*tF3c;7NJHYsKOUPipkrRVVo$!(2yvCQ@S6w9|AdP_p z;#NnNGAk59#JF_eXbSl|mrmmyz9o35 zvbFbaZ7N%ds}NGk4C<@kWHo9Y=3?w?P;JGSNx_4J5UCmp-2=6hygz3`ln@bT(ovX1 z+d?s*HfrrugjdX?e`Q=G(BU1oxNKtP#Gemx-A?ukgq{YCyQbW;tbH7q`xhIv0@o8N znHxi3aY>1*VF|h+Gmd%58#Msa43P$BaoD!Bll!b{sXK&+p|5bmxpYEv7UBe=XYKV% zvjr^Y-U;SkynF8cn@@C=HFvwoHfdl3Dz$Y5@PQOCfo3rvK9$^p)hvoUy%^0{AE&KX zr3^57(w?=#a1aV(E?rSkksq8~dF7Rkt2UkNY+xc9i48jPDK}=9cWhvgUn$!v^6IOv z`Yl-uT3rAB{rzBvfsgOw2pXSglbvnFGMW*s$i5JW^N*;H8wF0vP;>asqSj|0mnENN z^l2&jxJRvzQ(vw8OC@w)NG)#Oeyoj3S05*uEoEC%Iop~tZ@lrwXl}duZ=nDh^4D95 zmPD6YRXp>|GyS%!%F0T|iiAIGu8Se!7e^?pRyq{U!pg2G&?<>|fX;&GatXn+&pzu8 zIN$)EZWM>L-PEK>lkPx3x`Z<%E6CFdE}XDQ18eK@YyN$F>-$H@+}OY>+6LTO+VYGU zT`w(Pf_?<57EEO-7?&^dWBinR5|Kdrz6Baoe8>UvNo?(4;fg0>yK@Sp4D7Zngs29T zi7MiFn@v`_g{Lz@#;(M`Lura0NVyl?Om5U-@Pxk}xX(wvV5LQab1~89VC3hpq^7;H z5L|~AbwkhXx8I&WWG80Y9B3(9`((NM+;I(}U&(mrH}hB^s`Yk`#|S9Qrc>vEgYhW6HYk64^$*{ zo3IcKHr!;+a=iQQyZ$gav$IY&WSJZB6$EkrpMLtOA3Qzzcgy$!@L9O zeiZNHR95r5P6;M~H)#yS+o))^gcZVauDDGjfb9RZuYK)m=(H;|;SYfuZR&!Dop;{Z z7kuErfsWg${3@nyx6;+ua?|-Ot!|3ec&yO-)hdVLXbD8CQd*_7)ezqPa5}Iqkef6F zAx}@Qz4qFF@4fflw;?zw-#YWCv1!BcoWisV2$35r#F?|0kj{O43H|UW!)aw+4uX5x z=^Y9m5-~z}lmX6<-7Eg%756oSgpRoB3Qu1bk*}t-6x_7hUG3g?<2V|-6^B`$N5HAu zORm%JecdDOP%`vyYO$6LUuj0_fks+_;Yg^5qThomK{c#o%O*}Of^Y$IKAz&VcgR@1G8K~KA;wsy$(7)z#0yyFtEnw=CFT3NKHnmZcMv|7?CpA%yz1tf;9+fDWo!-IHkIWq#XiAR z(>1NO6!CR0gs6|u)(Smnd!oz$y9#q@9SEN?Ws1A>(n}pT?W`euc#qJ4#uySl*lOVe zKlnj1NCq#rzKdn1B=20t2TRpReB61yCw@@VoQPBX2k{|FbO ziDO(Yn|)^AAlU1tK~U6GVSPspho^xDVTrs=q~IfQX5*}jwc`5U@EG()8Fshozac5AexnZU+_fvA1v={37E?`yoy z(*alH_C2ONRWgibueRY27D@R3)Gct|Ll6+?M!98bNq}a3qy!Q?1Y)j*fcX>S7W3D3 zESUva;+Ra6tz-rC4buK4;?EV_lGWX>i2B>H$UzMg6s4mc&m6wbA+?V@!TUi)6$EA7 zL^P{RSbJBL2_zU;7YsC{88vz}R`tgxp}M1V=sc~hPZB=PIO7aAX3QAhrfqf=KWl)| zjU2R{V{Jk7#Gu6o-)*`ij%B&rP6ww_Wh0IJO>Z=G8(1p-An2vRnz)tN^ZXS=G^%4H7YCXgbyp9^;e=%^;PP#; zi%D!upo$hqSKh=y#gO1{wVKx16D1*o9o^vrgJ*Hu)oJYP)|n5xYEym$8XXH}dEAEP z!hS6qJ}n=lx9Jufa-FU_wJv?ya$p z3n98?=g)usvp+biZC&(h%D+|*dZUjnixf|mn}5TZ2p6#rK~SBRa`kPu=5j}E2ljEUvR;=uj8mt1nmV78A1 ztb%BKt#Vm=R{`QQX~EWuu0%IBFa#)XVnQjT#I4bfRYn;s)H27(X|YheILQs+bb2XY zx?;0&yh%`K9TetY=_y^M|4oDt!RHKe%E_Pd8KrqR`LD#R468g=(HIMY2p_zm7BWSc zV_0^uVa#5wyIDm$he+yFOaANJeHwgv0fh9HD;+CC;|Rw?0$T(Ij8>(mw{{iTm2Rop zN9oaS(BU0ZWtGU@;Z;)}9V!2~=hf~bPGRWc2LBF-chOHnMwxeLg&`hC-0b7$XMvv! zaPC*Eg}le9Es;&QX;baHmMxaWd;F*N@Pp*Pv&)J(TYHyP0Y5ov+OTq`?>OorwBcdToS8onzMpXkUZN(yftKWkd z{OnHR>X_m*WBj3Bz8d*3f9U=ZHTp^GcWRhTiAqYD0FPzKFjacs7bI+C0ZwqeN4|#{!kGY#3 zb859wLWmv_6@6ZR{q=rZmqh}(s!a#1xb$NimJW#&F~O1L{D^pvwRiL0F1ixkn7}Gq z`4UqQGpm6C?oQ;6Id^I z0msyJ&8HqrBL?MPGekR+|6MRQ#hhD8OztQ#$Bf~nr7#2uLFZy#Yi0ldKmbWZK~$JH zzeoq>kj5R-F`KzSStN9uw?cHKNtv{tU-`+aLc%6~HeU=_TS#xu1wM~3#6P2GNVnd( zvJtdd{rL;YwbHPJ?UJ|hhK@iKqGiGL1&uLtWpoij#y}N!xA5klRwBjOJW6+>M?pMG1l@G1kcZgp!p?<9=X06`H><4G)ae7f%TLSq( z`IQI4`oU65VZ6J|rXfbu|0n(Xr{JcZtDK<$T8Nc^cP386&*%twW#U*qb_9;Xr)Hm!D4yv zlLl<_Mbl{3%IU+l#}9`{ z3tP&4HdQ8-PpXh?$1WDKLvHH}G+=5BB=}5c@}e(u8yB()qLvOd7~bF|mN*l9acz{-bSFvjTIdFP$yw_?d08${@{MQDl!S`65X zhf$+O`9rQ#r%v_n5Rir^G_;+`xAHHD)6Ck?3x{TnJ>>hJFy;ooN?yBL)Yq~5B;Ut< z{(0d0K25+i!3rA0l>ek&)W5d%wf6b=6T_y70Z*??3}hBAT$pBSRZq-2sVJfWz?X{a zc-;jTT;LCtYXYIU?UL&Y4N6mK4;nPcaoF6w@=ACSQ#?Ix)$4!bjW=>W)1{b9XgDS{ z9u*I=Ud%%3#spSyd;k5EyXKl& zgpgXUZbNj_oK@jKzLS}u!qK%^`&1hi__jR2bxtn|x94T|NcA4h${=T+O!P>z;;@S0 z{+uubLG(E2h5|KtYxYlhaYVw9RzWoDTH{Jm=>g~DcG2C5HA7sn4W`7CtjrrK>q)ws zHU>PcMD57>IN4PoTxbQsuQceOw_Z9H=#_JUwn1$SphK>DXFx!oW~(6j+=)C^qHAF@ zPBVNK4B)~h*-QXvzTYb?O>s+JxXOR)t=j(LxCt!f9(*w1-IN;oK)q{HaQczQ=F@C# zY>EF(76Wm5l{w9pD?ct}Wnmu%`X+Gu)eqg&sO?t*(XeiB+)$v&3Y)~xZFsuETXu{^ zRSwZ^#TUQ$#eFd5*sc|)SMT0l`EE67wnN6O(j}E~4Q#MAx~?YFlo4k?!hy?h?#~G^ z)w+R9Vc0Fqm+};X{0FSXq}|s!<|=?SD}O#BDt>#)F$imY3+F1E%-%hwV{8#DOtT(M z0FftM2|aQoNCb;|D09@P8kPrV^SbQ{9cfQX} zV!e{-(}+}|2Cn2SE$*A@AfCgLPDvUPSr>Y3E*pv4mWcs1PER)~K7)bIv~3LrJm}32 z!TIN(pT8NWrS|LtH27JYFb$%}f5zj|p?HC2S-H7KSA0r3>WMxSz2RWS%Gx|ZD#BJm zypQ|lH@R|>%Zv5o!cNRfA96^F)uZ`t!GcAs)QEQ!9C@4Pv*45yj~gonj8^p^sM>eH z8#{Zk;wM7Y7}d8K4f1cg=_X%>1q&8zkkt=Epy*Zk(N<1f9$_!6M|o9;rFh@_ifb`asio1}<-u&i(zYk@6$^~9NihB+ zy=4{pwlGab8aXKa@cQ4Z+nvii1q!P|M38O3oUNR4E+qRFPeHnvtptmS6T_`md+ikl z(^CqXNEyzV_O3@k?}06GE;c0)N)JC z$a28e&djzIMP)s1brW-YxW3{=DvGJrB(){`4Xb}k#z0-!||H}R>Q5|Wo0S0ifJ3$WcY_&a%DMfO1H_jrrZd{I;P zv)eb|ih~q8C8L%vidz9KJcF<+Sm{?Ays_CI+j25@?p$Uu7x>#m^wN1#O0zuz3ul9+ z#u8D6SCYRTo}&ajFdF7f_93z5Tyc=;_3L045ebL4YK%GSGl2vHYl{JEVsTw>2WTsY zIvqb?u&ooihF8#BTlOBB_*vUC&jxGVG21}K%GXk~Yvrz)pv8;Bpt?Xs zGeu&)A#)BfHhg$rUd*kcQ7(2&ASaMuptTr?lU|B#OKQ`>z@TF{3|;_%{oyC&Ck;M! zVS{Y=o=#JV>N-IIk39J+1)Ey^FnbuN_iy3+M>9W6lyVeaS$0eoga>|Q4)g4{Q+Dnf zl=<1*tSd8mjh9*86KEa-#`~K0wPm`hEm^x#hoMz+zjs`>)(jXhAU}J#v7tw+9=w0x zvn6`d08BvsyQ8!u5-*!~YwI5+8NFG`m8aTH5~dLE;|2}t><&Iy^3VcSLHw?hw$4Tp z-{y^huwjQ~OmS91^crw3tAci4Z`7oTugQ}qd!NxNLl|lU{)(SS2|oMmv;5?o(JN&r zXOcr5XFxxyvKc>$ms`Qt8)$&PF@y3Kj^;721K5Zn6HQW6}Hb1%RA^8aV=Jm9RVuKmByy)z6Q z5fBhiuwcR7jYhE~wrFe^TVjczvBZ>@`0|qI=*vs~$$LqRiN@YFDi-WrG4_HTyMQ7f zy$;jvz32b^?Q_t-aT-`N2&~{>4;v2+(GYjmb6@PDCYQ z6jYMK`hWluqXkXQeM$k^L?CNL*`{-x7%*J`K^Zv z+EQCyG5VG3qHP^Ij4p@;exnV&WulD=0&|OOE8*XEe~lTVL%&f1Q>HA!T*3t_(H4Mx z_8H8FY1{&-^NzhZ+%J5tgv=D9T$?fAX_d2C(nGGW?ak_e2_U{TNdVFN8-mV8uga5l z2$(P;yMT#=GK4A-N(dcCGk*Tds{8AV0*J3{p#Vl#!cvy*7?;(b0g+)d1Q7OU%SqNI z!MAbSO7o$8%fw3;1dw4oMJVV34^|%hw=iT8I6vmGGWvqaZ%2X&AQxN^ zUBj!mv&cc$U;idELYuq84%^fxci@3y&hI0vEN7LZx9bGk7Ja^oe`^c|to`zmSDBZ# z`=-kJmB7xR&HRRH-XwRE{G)$*xU*?UGVX!g6D_qe!6qWK-f0lIh47km_dc#na#gsV zV?%jF##UbQUeue%KoMUn;>U1ex_sCwf2c#y?$2$t?JC;E-nJLrlhB{f#d7RZ1Y8NdRfA@EX+Ep*DXe13M=`G|X>VFfdH& zO7Y!TNLimv7mZ#2DSv8dNzg!~7T-FcIwd|poBZCGnFTLGr4T(U#g_-I=Jyik_UI(SbhQvLj|*C}`iUZvD{ql|n!lM+me6 zB3jpqB`zc=;i*(xv_!}u+b~;ZHa@1}Ujqz82yWn5w61^w-^bDZH52I@JcFJ54OlDq zF3=%KPXz{P(VOBFP1-|e4d_#S+)Aevt74Mw zg}Q>bg#NFTbC3fOK;|$BH;@&Z(q>lr^K&LG;-Tnaoxwo917gL%n`!%9SOHNxugBl2 ztE)R5Qj7=aT$zLrUO~4!`n)sA?=N_=i!bcss?s}?03w9ct5ED&7#zpUJmjz7o#~IY za!|CzRgkFd%p%%b-JWg_-g(ks!_JQ=R!h)Ynb~!X?mSL`{6AhVxWaveNMSyr%ry2N z#hY~tIdm(Cg&hl6g_A|^zjySXMtow-)2BN(c5L+exux;WJ6~YTx+kCP>5e%j%S~8m zzZL74V+JCC%*MPShkYB9k)nP47Ee(xVxTb$)Hr{DGpGnMOEhE$Bz&m7Gh7h~l%c9I z6*fM3{`u#5zvd{ej^5tSx@w8L!C;5@NI;Dq(11u=wl2N&Quc(iDZBu%6_7HhJK8R( zVT>YFesxwX=L^DH=}II@sDOBjUWyoKB?kNr3bgOQ-VTuP&GnetCeg~M>by5bFLhtr zX0kXNUiuKnrarE@Mb5S*ng}K8)D4epX{XO}Zq%r{k5i}4b~oKL3y1C8E3edW3zps~ zalBiidu(R;w^d3?`ZlxDUp$0fhs~m?AqKeFN_{{;+pmC5Yi#f?f1(Rpv9vy^VffWy zH+q$T(SVnzlha!zez4kLg=CdwwKEAIR%q+&#f&q|2+g+-c7XK@-W1UJj7R{nuTAR; z*9+g8(`0J}9zE`x4&cWcDV`Pd*KvA(HdtvYkZ+m9tHbSd9p0PLwUlTGDu=uYtc+%l zT-*6C<}9W>hZ}YeIYb9|6@9=>R~7EgJEy_8H{e0SzV5>ha|j?B2nKHW@b%q(`^|9A zKD(G1qpp5dNld#AicGl<7e!M83|RXOD-rF1)S_JNf=rOBEph46Xhl%#ApydFANLAZ z-bNQmSg;1MC9G9d8p}BM&O7fUT$?s(U%J2;XK#WeS2CS6i{rRjW{` z8dqrJwjyQJA*g6-9|knXj3|VKF!eq#0B|b7YdBnanM|c7!@SiP}0ADANSBh-(?A|KQmUm zPQoa#wA8ZY+)g_U#P1gfFU$QhA`@>kR!g$D1+sAtb_tibDz)qMv|#D{ zZ@R&(90Wa}GyLdKNQv8lCkX$=X{zYz+m?BqF;2|_YrrGb6v(e#!)xIhGTVk>HEv;x zm3JP*>pccho;8h|UHoOR#NW@mz%4ggm0pA*2HJ`NYd_YW)BvT_$7N`6!3G=n z0lo<%ZB48t8fa_0x^3>0Pd@R%C4h+L;#}v$9!Pz5n+7sW;UMzXXuIIb$wm zpoxb^9ytJkqz`%N4Bp1=`Z2*~+h20r@(AcfYj{@4v-A zOQc1Qt@IGZ8*tfZwR?$|1qQMdyqscK?kLCCL>W!V)RnK5shD?*HL{B_>oLK2Qi?5A zb?!pv$dUaxcs_z9>0a)jg97xx@1s>i6zsU;2AmmD))skp@0kCqzXYClE*jQl3?#`& zW|lns3P^Bg@YxKS90T{LjcN_CE3~i|k8=w2IA?3X7^pGRru5dtsSNW*57QVDYSIpY z2_0db|EORoR~v-$FACn*6X_6biP3GH7kwx~OBkrns9N&9c7KnWmNl`c5Af}YHvI)f z_vei?xaYc0kBJ$&<_Rzw@Rm;Srvt6Lp6qj?{3c*F69v)U$#(Z~iCAN!CeGq6%RjKo zIekEn!11VP|88tM-h_t-`_aFZ@QQrE?O4$SoW2hl)Whw+Khg^;{XKiu%X1vNwEC+p zi4R5Nx{Cqh`62J~zZC+u5%!1& zFpn@}pJQmC)eH-~^0L=_%t~Je(DiKL>wc$Iy*xT{RgzLtlP`>O^iElAg zFf`sC7U$U$7mtxnX4z%-iEc!CqMMnd*T$kOx)m|d8VnfS_!V&m3e#z?Iz252wdyCh z>#n=}n;;RN_RZ3}7y8#64HOzj(x3Gu+8(AYSQ=!-H~)B2C%w1v?}c`BqZun}>kE)f z9J2%v-GC*~8AlUHQe1aod5ga4zpjt7)%@Ld8_X;4H$%84s``z!R9C_rC=?_2r(DNeisjl3yb>(TDMF2^bP{cP1heFh#3Xf9$qZgm=;{=F6+r@D85z#VMds@29CxD6aj(5LlS6s4+#7GyDYFrYS^t8z=T8@X<3)0wH^l9wYAwwtiBZaCLKoa;9CLHltPgic_sE>Oz$ z3i^@cEV=>9=ZC)_UE`{jPYD;lkGD*fs}V0U=4_cZeRUOKO-Z+irxj~TIFS;qs9kA$ z;Zg<#ugDDOAi6hPcinZJ<{XF(vXn91P$$s&Eb+{bSa_iNMdVQSwJN0oNePrkR9j~|dV|Rc$yantSP}Yi2#6ZCq z@a=iltXUerrQUt_-PfU8ZP88HfL@2tk2~%-AEC_2^HBw-(m@_2XebZ5t?uVP|9Sp~ zt~jr0<&Co}8dq0o`xWNZ(#Lfs0mK-y=|YHZ!qV2IH{*HoBL7FFzd|{*WG9cwiY_GME&8#UASK8H(g-6_kk?-O z%5AsZ1Ma{BpKu2s{BO>NL~6{K=iTnRKjW^w_Wk^fQs2Ja-NhHf;aLSKD=RW*;j#$L zVnB_|RBjrBjrCnGwe8GX!Q597F#I+wHF%xZDq|bguBQwj4GtYS65|oHXM6|IH47sE<412=#Fj z-MQz!%&k`(Ndwa96kCzSgXS_z-PlThaSN75RPir~0W~x!weuXS8=~11g2r34cfV?> zL4CZk;I`d%TRuA`cfayi!yC$U|#gZ6HhQv$SsdG zHR|J3E-NcWKUS5#QlT>mAS$RKz?id;BILY>s$ejat^jpI{8f9--RwT*)IudgnT{4h zG;AsilwoZHt0tSuk3jibPCk`itSZYpsAaKP%-PC`v3;N=>3;p9gLxNT=r~|k7vH2w zQ?b;tqOk>s?skOf?&Q_9X*=P*o4fq-fo}5TXv=O{S=t?bxR~=i1Q6b9SOEpGR8tpy z(Nx5MVLjg2p^rWiisR@gPefZ&r z{R+tN;lsJ`WWM6@871!$*tMTr(2!;#*Ear3_|SO|z5TJ{j?R%ZAoMx+*=Jw7`|g{` z8IVnw(cO+MYdiZsZstt&al3Nu{1#j>U+w+sZ_hmkxd8(zc@t!5BP;#IEm(O(6%9!Y zM2(HkfwK}B4s_?1E?qj0_OEfB05XVZZ4}p!{@?%opT9K4CJ`bph5d`}d1>9>sHN>* zgIVfps0IxhN4a3f|a4Hua}J{i#+gL&_pPq;aa0A~l6^ z3Zo~*rLA9VnFRdD*KU}U3;_}65d%mW&X^t;wiO51JOw@)A0N zMsIt>_(iXS(!@ZhZb(%3eMQrXzE*URBoV>})3CB-?i%-jkER>;g>2&B?E~dgWw?hK zt?6+ZDvvTLcPnfBsU=NJ(U^0cUkM+a2js>N=RW>82cum|hg0F+e*1IoL9gKUk8R!T z*}b3;Wv32iOKv0gci+u}j$M`@lq{n0XXy0Qe=H;pt%j(1Xdy?XSC;d6R^Fu=LyPUYN zvNkWh2t^FE2LslY)ED>_5Oke9e-QO8)cUC0!i5W=8Rvfe>tDNXeB&E#haGm1wP1Mt z_185r@>{y3Hw3w9(4zs%jw~(oVD?Hhlk_XQ1Q-o??zrO)AH}d?!!)=G+2Y~zhyRC& zM=Kz*tB>>izf0{HbLmgR1fO%_qMT2v)5v1fq`yMn;a*ePlQ(pFz<0+2Zu^>r8nYL8w) zli7I7Qu?5S4to9TufLwiS*ZP?6Mq&;I2t~DxclWVf5~~=Q~cQr!W4u$UBCU?-~QIq zpiLSi>CtGB@vz?p`N}J=_`~kWo9HBH=sglvHC%alIZrHTC6Lw}t*H8bm~CVq+8T(*0v>r0WleSiVh{ha#+d7*PHDR)X%^=Hu>* znjg9mT`tMI_32Tm5qE6oN-ko6uYn1h#dARyX!Ja{Ew@U{a}Utww2~qq;DQSp{npS?TRtSLG!#IRgCAYHd&wo2xS>ObI?ce6Pvu|JzB9EZe_*TESO((-@oJ_-<+sYpgHDcY+%+KAQxREN7g0XXQ;(Yo^9%?szzY$;rl@h zd8lG;LpK^vnUZqj#wna;-E^chh5I14aG`Dl%6peVp2)lUZ(lyyi?D8EKyAQx66m7W z9A_tLk3atSG6eGZuf6u#EeImJcj?ll2ejb*s^9tE_rAvk2p_pW{NWD>znifmmApUPV%yo+;t!$~$pwaVJPpTSUyodi^HE4(UO19mYr=}S;I$L5p<`Ll7>dm9%s zn|ilrNMbE@*YeseS+&fvDzessGEx-}kLEL=x5Ms=OXp71F2^&6`*ZgiF9~#E#+3V? zBjH0IvsC=`NUIWm#gH3Tw3U-jg@E2!=&pE-xhoKO39}qJ&n{aQX6Y2NJ$ekxcJIDZ zwp+J7vXzzFvA1}A{IjzJe@SIwdiD&mefsnz%(mH*l0ERV6ZsxYxOBq5&tF_0>JEGz zCF{z2DE_Up7*JbKyP(auS}upE=DcWn$Mn4qFAZziKIvyBbD~XMo!y^{c>l!t3pN8H zwpX!jF=9i;h=s+ZS@uG~!bvx?!fq4`AICCHaJJjVPgdyQc?1&ZOW8!Ze8Mr=Ctusm z^+`>2OC*(x@2E^htENZ6ivL9nv>gM+KRwM5jqe|5v;U&sn1!90|8!2mhXxf|eYo<< zD;aE8`GJfEAdP_rAp#A43WX${SeR%m71>^R;ROzUbMhOIPy{INHgvy*0nrpW0-_mZ zKuQ_3GGZ(U2;&d6LwA0{OM&uzTt$WYI!7PZoBnDWl=`b(GGVwOy58zpv5%{+4zpdm zhS~Mj>yG~|vSnoovun1tb`$!zu8A-{f68+lmtO&CZT@{EYvLaTwfbBWv%j+Qtac!f zPWy0~XE|8J;qDaI?!LlVkjL~9Izn)!kApVL%E}yPEZhkvoZy8IZ3h%hCFx3G1dY*O z^ri5B6F#){N-LBXUwpBD&@_>r+IGNd%&R-^yt7t6f{#D`SUku2IHT9X6YT(>oox%2 z70C$SS2m79RM&Wh=w8gnp+YdnVrq`zOxpdxJ;zSaGIzKe;VDT&ngf{ge1L5hJsDhV z!eB={kzomX^AP#bp7m1qL$|d%ltD{h;vUGbMRBBK;o~9ZQ%`Wu;4jT4EUmqep{S86 zk%wuBFQTg~$-zu(D%$Ws7%^mj%MiFg{_+r%MLJ(`DJjvCx|WoC_*ocUYt4=IfzNxPTyWEqr4MJ~Mv>mCNI-leNuWGB0dn#0|O zT^_8RIp_7%+#5%`(orQY&D3T#WXmFLCFJmTUw6G5?e-5IbMMZZ3Y9v)tSrVUc<;UU zysiOPf{A*!hJRw<+LJDL^wCHCb`i}Iz4)TYkD1dYRce`@b02E9 z5B&Kj|E?9CU+U{g5>UXGm@(x$@Z=&i%`TsdoW-$?R_^}$@8^6&w}R+K0~c+<(zXqO z;t{=Be3K?k;%3Ef`fu7IGH1>l&8jNjY5cuKUe(WuR@6r)X~xRv!#49Lf7X9s?yk6r zM?JWpu8&iHC6&#xtij5u=9?SzRX#-WPyX4lLVp+#R;wgEST%{lCHz@`st%rVlqw$@ zdnj)L(<9qj_%EhRA<@cd0p+TLBGku#+JNw)wyLurdZSKsyZoVt9=h%1lTZEzG&dNU z$Uzgn@oHsJTdH(f1FI%%0o=r&3lYufdR&3dcxjbHtB*}hDtM3li7pkjwqQ-1IMF+6 zLO|h$4s>6x6B?hW{ev@e!PuKRWA84g>lt@kl7hQ z`BEiHX~3&JL>gI_GFaXigkK6`wIMdgtv0DXjFGk4QGsxJJ$LPVkM{<;`yfU~icYv~ z3mA$lJPm-<)a#@_V=o?#h{fbt8k3X zf)bZfBH&>RoOe78{HxP~px=>vB5o#@y&rA!^6egLdqLT=86dyit1Ju`-kr;Bcs;uV z$j8mvM5}U1t@1`onnmbb7_fG#2B=95Hb(U9I>425f5k28@=^M@^S5yQ&z{U_soAa~ z+`$b9)`vpXbl%17KXWg;N4w8-eW8-%ZduJDiP^?XwAm7i35$s@(_W1PY}Azlc{R)2 z*kGjJrn#x>$C^tOC&%Wi;fA}pK>^z+GjD`*eziyL{=1KN!`&Tjnj+ER6wzz4jT;k4 zq);?Af&uX>t;jwFFC0bP437P%ofL5xA;V}!xbifEt^sgY4wtS6&Mv`xI0_VofexJZ z-V#py4^-X1K;oxK_PTvejC#jmsC7{@`IfH_S?^UZ6JJz&b8O~Tuln1tHTh@ zSRbdsyW9kI8I0@={|yg{lfLCg^l|^pq|=`s%*n>H=-T-1ug8@?f$1162?cE}D6IN8 zzg-$j&?N|#zmmzU|0m~0DgOu}la2o6xutTnHUpi;zmVYExPF1V+Rx&It~^F)xe&kB zmb(oVAiPM$<1yEjNBnwDm@pw=vg~<@pC& zul}+5=9~ZRu)_|^ahsj>ams-DIBVmpw7wI>+*UNKv~a3uW?;iaPetj8>O$zR@259-g+w zBAf_6Mi`1pVH!3fb4%*-n*@ci?{b%24KrrOayo!?o;Im@wKZDhqzIi318O_g4n?nB z_HzBZzv$+vjrN+JKIxJPZpwGhb1#Qu-N@ibH-b%AgIEXY?q))@m*ie|S9SfEJEaG? zk&t`!(h4_Y=_1`vny0>&N}OdnqEGjpZbN2?D-lShEuO*rROtQ^?CwtI_ARZ1zKOu| zpqu9Y+r1rjpr`!AGUTgu|P^1Ud46jswl zGd7^Hi8kihN`|SABbr@MdS@VDpSc%0bOz&xKm4Kl+0TB)<9b`?Q<4xaA*8i}NW*Xv z;_truZg<*gr}?-wldGF~v>Jnh@?1sUE+pLDapIa_lfZ5?WBrr-w*-pMRE&p%;2DoA zoM_MbIDHd;5y-#g#nO=D05eQXoU>j7?!NZ3qM7u-%kCwbi{}e-Jr|vY-dU%Xq@-vn{WPD=PwRD z^w5w)=mCR&E(jXp4~1WT`Q`3czxtKO<9ggHtqC)&g!JMtu6Ch7Q_a$@z9&JGb6SxU zoPYlL{@}ZW9tj?gKmNGi0>-D1+qwb-pZiJYBJ4}ZQ#AsU`Z%@o)=r8Oq~pMdw&P;# zxR{hTDKQ;YwwN=sBYq`Bvul6D%ql>{aNrO@cf7BMC@=%Oev4BX=djmX4~_Nq!SIQo zAVEYB6}-McV%2MBJtDy);(1Gn^Erk}S92TigB*O{knAibHs1SC1{KstNhA3qjQL9J z3fNvLGs_Ya$cnMeu7b6E(#FT3d&JZjj6BYM{+_V)^j~veSJk_~Vh+PvA zyZg&`OIcl6&Uvku+zswix25Zm`^Z(KE_6G*^nPmoPwuUqK5tLgujXYpe>qXw0L0pP zv3(oD3)5Bq;?YmS54V9I4vvGVjTCWNheP2*O3m14Mtz+23sJRPg3f!Ow3X~;6QV@>M*m8)(DHO({#Jfe6v_)AN-3KfcTp;E=Q5JGsV0-Vyqmjr zs@xe5f%}W`SKg5Q>hrn;)>e&P)i%qv+itrayo|2)u`SybZ)5xL`mAh(ytNTboH)_# zyYIdpM-muxa9n4(IQ;G1w2CPj)2&suDq{5OJ@6+Q6)kJULxPL87wd{&!MDHtZTI6J z|JaQlJ=zO2T2cAi-~Q%>k&z=u=05r4lhnEAo;#Iv&gNFE4_PG%pL*&kjZ4+erIPfj zGOSWCgs##=TalRZr^=R6Ws5lnNQ+w5znMxIj$H`UcaskTy=^?*^F-&4cO%@n+`2Q; z_3>v!O7mN@B3wteW<}hkZly!X1`K6txfiO2;mRWScQ$SOi!&br-M*!_NQfc1M5#Oo zu7Ww2qsuPloMdQ9w6#QRb)@)WwIK|-MobyIwqW@sYCfBev0dPorHMhh4!>*9uhi@$ zQ?_!bOhqVSKp2P!Cu*nZ^TVOaYCA5Pu9w#S#Rx(c;#+j?b#4<%J>z^|>IW#=mXQP# zHl!y74*8Lrt_juFt{ZN+!EY6jyKdj&(KW{)%2qy%S=y7S)e=@)u1vTH)8lSt znDu>LLHe4^TnpN74ctFJR>tmPHDrt%1y0IU39YH$)l#mNl&IRDl(kcYWLL&D)+tsR zeuMyUF|?y|TkfQjP6~LGFy!ID0D3VY-ft7;5WBnQo_pL)H{F!ari2S^wUpp55N%5E zkl>>5I!j`}HY&$!?Cv|?`HnvqCU;BY{rBGwd67NTL+?_yDAIx{GLri?i+gNa^3#r%eEMg+-n_;Vox8EojWGaHy}&xWaU*oHBR8P@>} zOL`-SNOki9NrZwLFK|?I-Bzvks&l#TLayEVj7J7Vi<*`yVH-ypTSl4El7r<y;3peGtBcx0#pBlZTnKa{^cbB=nV2oGsm|41n4%78bAbSyt7%&VZ+ow2b z4P>)xudw711pM_FU4?U?ulL=t!6>eE$hjIu7QEs_`;8iT6nrLYA`<*(pigkqxNUBl z#owrY7Hb*@A#n7EqPOB1g?`Yunyam5k}REOn(MnOQ$L5IZ*-rB+@R<8O=iN}uNLMq zeuP7rjTUmvaTFelD|uegoe#vBc(GT8nCeM zJo3f=pZ@eGx7%*J`4y4Aef#=ZQEl-s(4a;u8Wj~4e*4DfJTI>8R`=ekyImsCRl6EM z;a6`PC~f6SApS42g!EI%!~N)@aS0&m|Ezzo{NhwFSlJ8@NnPgT_ktsB#-s9R`82cX z4a;Hvnu%`B{$xT(=uT%LIsE|5aI{BHSUVHc-&B(eZ!U9Je`KYM&WWtEf{3+8(SigJ zwT}&0p?C!0VHB4-n;Ba~%L4nC~9Zc7i)sT}lW6nDw_-nhA==bTTpLSPVafP9` zQC$xlN@<&wRzF3j2BK>L@#k2{OzF(YfBn~g$#0g6T+*a}9d!RoRzhmg)xTLEr?xIw zEp5CG8&M`YHr;%|ZBWAkkN_XQrg!WW}W4 zMbq_b`TXFfZCL+>yO^_8v?{ngm27xO>N1-h<4S>KzLRDp)}HdLl*|?*UF{gK_9*5o zl`To7eh-0M0KsH>vVghJWnO*H9rNbaZv8V;t`tAqq)A&MpuhP*C5R}}k7Jn(SPoT6 zj#q;V{WdGDX!P;qC7%H_T87vmp+m`w;_I3wE`D+#Vino>zjtrE`)fC8Z&or(XNo+- z@O|j@U;>KhqX_B-Q#ci&l^7@rAgx1~+MGamQ^W0n0CGM0F(`Fh8syxE5)ph4CE%S5 z|MGH5hfoC)EDUsLT-!6Wk}-Vv@ccFot!n5^0==-T8{-VL4MZy^TJex@LLVE`jL0LL zM*lVgzk8v~{Q(4!6JopiL%A#Ob%ckdm0);C>N13A~Ho~1VPlx7*f)&Kdu$Z++_wcI>Ptd;xlzK{W0Gnlht*%R>T*Xj)3QKT3(V6{kQG2|Rk3 z4(J%}sbOaX@~DjW^!7ueNLPX%X=p#A?bLd>22laVf@j(X7I^8R{_5 z<|tN4K;_d9W?52^f9u?GwyeuDlTR8N$!DyEoH3z0C=hqfZ~w0L_K z#wJ0J3W}>PCJMbGqG1YFt$J7k(!smO$60t{S4EK9IF3nldj@oTBf2IH#9J&8j74ZA2E-$aD4AxYKB#pYZK0z8#N!1C&668eP|`ts8*^30=WEWr`moMn0PO*A@-p^Lx)Fc(7_u zRDPOPv@6gc;?Fu3J%(@&yZUKaw!dW9~?riOAO{;r|-i>%MfnDM|B5b3P^U zCFl0DF#ztP855Mpd`3*89NbNe@i%}t#%s)wk4uAmHk!s|8bD-qgLgdPK8CRNy}7uR zrHMKi(`4Mqv2A0vD{>)%Z;9#{V`et2{RYI}>jQVyr{2kz zX%uCt!-My`cK@jkrG)=p(BI?edq5|mUtLjsDTDpLJow;)2XQXs90UnXPz$WB_!SZS zc>w}iioO=`&)Cw!5Zs4XoW4zRmSh?S;;-Q*tovDs`6sI(-^9PZ>w=^|VARC8#-trF zVL&wPgThPQzI56fS_Ca5_GK{h6qU6TW*h8Z2vW6uqCZNHH|>FbB5HqBL^K#^C2A<* z9)4AXfe;^40g4Y)A|JJvoNZfBTgV!Sf7iqNkC9}!Qo7HfZlgnZ8GR{A`MQaG?TFa~ zqjSmE<=B;XpEi0&g5{?p<7_T2m7xel3>1n1F+?5?OZ^VI_%&wF1o?Y;c((L}sa&Lk zTgk~K!#BDx-2J$rVUHua1>J^3WG$}|%|so3WWmk1`KaN0Aw>>;9!PFcgS28=40VNEAn)8+Ig!uth7-b=C&- zNt#&=wXa-u%y_|fVi>|N&qrb@3 z{(pNg&ZDA4f!ve2m=W9({~xRc86~e)W;7eZS@$*96`Qx{CwuU*fcsH-nC3 zRpl!BVDF!}rjWM-_%;pIDf+m40mx8i2IW)N?}{mlIs3s32-Ql%;n+1pq>ZkU?1b#@ zApHO;@hHrDqPn8;;A=ASr|ONZzBa8=JvYt=hzuI2yh@rANK;H%f`|O8llYqSbYscy zF)KbbrVsJA59WEOPEpz9qX^?VK~56}0>8!)(H3-=Ctd$dVU~ndpU(*@ zAN^bK{(b+*OrD}3qurpe($y?@wRFK2>1=ue!8XL*@^(CVyA!+eB)Tp_+c4nq2fr=~ zAcf=4(+mQ`&T;>;ejIcW^m7=3!rR#UqSVK!v4h^U`nID{qfzUj@<9J1vnL$P5y3=X zi7pa0A}t@p8}RxLh6joJA%qkiE>XS|{%hQ{s;^ZLlW3Me-}~Y3P2ARMngIb`_y|kR z+=ok%e*ju2ujVJidxPvX4^V!PRL(oAGfu_pekg64;Ayh(pMkq(a`b&%f3W#uYHzsg zj4nT6PsQJPf5QuSVd=P?u#e>n;}ySE=dmDr_3hrTyr;(=N4(SlPZw!bf_?o#?Uk?T zyFa^;DmjO^5l#^e!tg5&A6yo=Ygw_7#5iutF6r=WzV*gR-h<~|_G4BtCQ7g%?lTWX zV|*o6Nch0r5@fD^Hk%%I4clbTh!=&0hPW+hUl8MOwq|jNZ+vYg{m-uMiq906!0;8> z*2;g;pm0$FU8v6x9oC?DPbMgbUjH~^#E3qe)hOfEDG4fB-tO6i8>^1wT*jenr`im+ z#z1lU?UnYJ9GZ*(06+jqL_t)M_(X$BZv|lG@@o#6KlS93Pu@Lt?AZ745RM)_I%Fke z8IOo|V>M@WsWoKnydW+p2A74|l*KJ$m)*m%I=(*2;u-lF$L23|!Mrqo2_t-l0_ z{Io?M-}!Odb@Q<(9tmLh9>Jf1R21749|PcN?ePaC56Bxvy)OIpAs$_j;HL zZzAqr@m-OKCwvceA4;^ZWa7>iQxS?7SOpk>KF%Q-2_I^c>YtxsF!OqBu0WRnqPE(F zzU4Xex9Mj#qL11UTKgvYw&?4l>iW2*dpV`CoG?@H{}Sd`pxG&;H(EO*A6`MD({7(@Z&KD`!_yxSb2S{xCo95mrb@^8yGug3*Niel+x; z>TJ|)M0O?W^41=4oTGfqmXzaO+G1KT9otuam2)V61 z=z6L*i|ysAKE(WNDx19kQD`U^cu+1i7eDsFM;7Ue`+kD-uv8fQtSpr|E_7%9i>PmN zwOu|6+~2FCxYpW#Rgkusehd95l<1`kY7VsY5PB7l19s)+s@ko#+Nx~#-FGkJkogqU z-+3(XUhE@zn`0lgLGHrV$zk{}BQDXqXedn_X!QYFQ=_VZK6!|J_9V`Bya1>`SHHl` zTOXWx=9%i_mYsOwi6tCruSW2wK>(58>CpB+(04~Ee}eC#u0+j5**F&C>H!tFvx8N% zBus6u5@^*p+lwtL*6*g&aQH`3+IK)=YAF}`Rx4kiyzLb zt<|8Z5|usNZJ4TpXW!z!5Dg~jRkxGe4vl{wdLPO+(dOaI80`toKZm(UuOOias{{k0 zjVu#pyU->Ni&OM&w9PB9tFMtg*@h(~YqD)I{n9(=AEEB0{cb=U+hZO?*g@zT^!7su z)TSlG%;nnx{7L@+{d3$Dx9CP7e~Mq}T0e);`%#~J2+CGLPQt7_et}y1X(kB4QMLO2 zQj~a|CMjc>3=iqc7(yrx#q;K~U?9B4po9InxqOG;fT|#5cZ5IeAAb*sH$!;t>R@D# z7mY{iAPkqdKyiEN2L8rkZXU8ZWE1rtAj52Yvdah;@@N*t+v$TDd z4?Z*4*CtRq7j8w9XP{&kov2NWXWTgnUBXBe+Z@xVEP_D|*JYQn`Vn$F)#rShhI*bA zk{*{_a>*zTix20*h{0@|?8DZr4d4X|A41zX9tNGm7OO=I7A%>Y)3i%)S|oTkgI!+>^w0Fmv0GJ1U-`nWqz!014_ROTV~RqYVhtK?|6F^o6E zJ0LSM57y3D{-B$onHuFgnRl!!KvWK5+t78(?dR4H7rN5Sn{IyX47XJLz|xW~x)pDD zA6k6^{dAPvOPH8AAN@I${3y+pf*;~t7oep&TNiLpNEvOga3t-K0b;b31ATlf3@eUg zA1D9GK2C!b4RmYquMz4LlyKh?{*!H6amp@wl}gf#eba#E320lh?%zb|=A9w*d1pi0 z7hvybu>6~@_>*~VPkPP8*>oBqhq8kbCQ#I!D~f7Oro^Db<9%6)kZh)ROKSR(Ta7M@1Ds316pRa(`k1Ky&G!sZwUNl1}@gE9vDA@M0~Wxtwq z;_H*V7w+N-LMB_g5V4Z1>fxR9f4XF#EY6GK&-%1k)!3ZvT>lxjB(($iQ399|lA=3) zaHx8b^si%KGycaT&?}{^-cdRRb;>?%j=%bJJ zar5p+)eA4YptOu;ElstLdF1OJ=yWg4zJ9TvgZW;4i{0dZwLoVVK!h+YbE%T^6^(fg z6@O=pG5IkBs&WkZstgFkGjr)lrHYo_DqOn43Z$#SnC=@->?X6~w=^t0oP1ngpi8p+ zZ6SFND&j^w8!q(8@|@J=p7bO8S_zThAtaRh zHfC29c5q>>?o3zo4b4e=Ta$0B&J?e?o8H#=jTY$~wcPK)w^f+lcx=qd`GLP^Q|VgT zqN|hyg?ru7T6*C0&Ri_Ap6ixQxg6`#W%Qn5s+1YEoGa7HhsQeAT-Zh7`LH^XSv=-230Wt8%9FnGEgUZT}it%M4`#Vqn(4CFLxWcl+Siho56 ztON|GA5t3~i8>{YUn5Yt-G&masGs)yC)Tb15p79WP+ybm-(=P|DYao!7=7DU`!{-! z-f-Yzv}1mZW)!c|x(xMAl)+ILwAEueN@*%wTfx&MflXIDNwebbC{Vcsir>uMA^yv| z8UN?d^?j}+!;T6~PO=*`>u0iK?-R2wyZObgq>?c}2{_;!NGAPJU4)f0UAoJ0Jn|?0 z2&P!k$Su_%FoJBCr(H=61H~%vOxzisK}te4{HrUezMG~&zKB0l^GV(3+(I57IGyhi zA4}ybT&~BL`R|$V4Ohy%Oqiu@Ri|90(Xs-SDnhU+6MV6LFv}P%p?SlOM`6Yp}^g^Na>Ne##TP!ge*b%tY?JUh-fK#&oM#vr9L#0?90}Mz8YuUt5F>M& z|E(?+ftizfnyb?L1j?9Mz2Y`mD6+!F1t?iUW0Z9O3Yg{dM*5AEV}YxxWQP2 z0x{tE9C$4rLT?^nYTv#M(r1*@Ph5nZL7|@l*R5>d#jI@==1;nmK)TvzLp@15=HJ37 zoWSTHNh3x#`eyFtPr5+y=vz@t;BCf5qO`K8~P1e#@(s&FrSjUq?f- zoJq6Nv9im+z`D*}XeJ5?SBRg+(kmofA$}T*JDVPRS;}=`oaKcNqEY?$cbhPVucEGI zbaMxX=+#C+%xDDV`d0(X9k9BS-k9>9I`~id6-aMP`HyY*^L+vW!|t^AM+m=u97iSe zdl2Ki=dpV~=vG#FF|%}|QETJg&Q-nsYF>GUp5)I1@#;)*h_In@G^A6bRnwE@R(Qpu zhYTW{@YRrkI^7 zL*S7g>oO;meV3boD#0qk%uVUZFfuDcYDk}vzX@WV}8xP*63ROU=M39%$zkXOlPm>Mvz&lRLP}5ZTag~ z$aZecQoSXWXGFM}zOw7p>Qd<#&8TQ?sncW}rz#Raaf(JYllvt~<+5}dreSt{H=M}7 z#{mL{xp_P81`5}ll0W;N3?sA84~+SGX!Nw$F83qhJJ&Gl^cv=-c#QlNp@@O@U_fnI zZF2zX`*B3fQ2V~*26PSVlI>4^lb}9FaTrY*y~s^UU}lAt+2WAioZ@pmzh+l{wEz4j z)P5+V@l!G1ftrO%(mB?G!18HjkZ#JpDI%FW3L2JQWl1W1S9;QA;5so43_JcEkSJ~? zQRq7$Ex$2h?|>8@x9~j^A?xbeY--#&DHoXVK@@sRNoc5f5lq}j3%#A&y}aO2PCt2( z$4|M&Hw}TX9;91+CUt$(H}fZZd%)Kf5lEM6Sf8>Rm|wFOtTQG(3C|KfY=!dvSolyJ z7O&}Y6PR6jurlX$1dP1d`>y*_xaO36+II_OX?eVo-&+}%{+PaQSnOdhCK1Lkj=mkc z>O;I_m4naRDo?fL1eGt3K#+75=5Z*iXzj6+T}VqdCam=!SvHx?-cYX>D7IMTC3Q2) zOVXSS@AJgqMTZF==EDZ@#|#z!EgI-V>@DRx4wBa_?+Pd8kA4!J)aG4a2H0!)UWr?& zl`9{xeTG`fSc2*fj&-2!GnNg!EAE@N!fazN?Nq3RJJ%C_)cZ zHT5`|dVN5&r8b(P}Zpx2*Z7MQ=tR{U=X1J)LbDDmxbg55=ogj@Fa~?`tMl9~PFJ zwLe`xTV_)XuvIl&`QdV}v(@S{&YUNtx8e(^Q>oX%jA2%0A7^8i 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 dZ)o*=@Sk~0+Z~0O;OXk;vd$@?2>{7!p=tmC delta 27766 zcmV)VK(D{*ng++)0U?P{PDc$28VUda01Zhr7!%hRjYd<{=qFfE6H6p9 zG4@0SHEJ}rsEAk)5Co+6-F@%f`F{Voci!Gzc5y*ilrX#Vrk|NPbLPyMa%YU$s5W9? zBL+5NU?T=LVqhZ%Hez5S1~y_~BL+5qVxT245T%)I8;0TOsondvWH~nG^*@M#@PDw* z8>{>o#6VeDSzC(L^D|hqjYZmkF|bw{pfxsVh%0NVTplv58P=?kJ&Bv7YY4Bo?&jvz zTzH?ec*6NgZP7>_n$;eSq+1tWF+gk91>n_zX~CQH>ZDoQaTEGqzhzk4by^L7c+I8V z=Ehr%tk+T0EJ0=Mjb)b&Z;YWfmS&@0lNea99l6O;)emq?7zuD^rodHc(0XCaS=y^# z#Fleq%aFCVTnW}|PFZ@*+8Y)s88OcuXNC~Yq zBe%1;5noR1$>^pA&W?7nC*hTUy_TDkdjq%5ayFVmN&VP4+fn_{+g z>Fdg;@FZ>Bh^%-%u1}velkmFu*{}xohNDH-|nyDUOGmlwrgG=(kg1PbAV+(NsczpLH{)Y+gxN z-OQ|E+=lh^QoD!m!|`@X@wP#CLd)*s zVQpb0gegmFPTSr_mA7WIx8~%o#c0Hz{A zalKmAl(u1*HN!Wz(TGQXtvS7;(!-e5*-Ofry1bLJ)J@G-hLzM$iC2zWpAjZ?sB4Ud z?ot6(CLT#V4v%S^tOTUS&AJ;BgWOpHD2t-#LHxu7rOFed^y_i4Iq@FDS41S0%|G#J znO|48^(_H>uAj78&&?nM#B$E z%5Zk8Y*o#PHl%Hn%jJHZ z*$flyX6dq&7qoj9+To}H%xvE4-tLiV%TE0w4Df#CNTa$+!xX#Y_n041OL0@CnZ!M1 zOJ7RDU1t^Zv*BrfXC>j4z3Rg26G{@EZN%X{yr0Tq&G$C-X%qibwiLl7_C)&h;bN8= zb?e;8$eYAH7pdTs_H|+3+Z3kSrOw^3Gn}yn;gZ0d$`T?T*#_M#jp7aE?-`beqeQ^i zC0%z$dnGtoSQ<4e6bNCA#n@K%j4Fkvewz{R@7UcSftk&JE0b)UTV3Z_{|qpt*|2gX zv~z=M-8j9TMBATPYKQFLnlPX`JZ9V7_lYbvJuN1ARdy*2*iq#un;Usx{kUgsSyiH4 z%~rDc3@fJ`YG0=Ww?6Dd5Ky4{7_^+}bBddVrG8MKwiBrCH%lX;8&9hW6zzFhk~>Sk z;s>SjVHe$hW?`wHZjLru27xXsWzQ(}i&Ec|KXJyuEIp303^_{*R;spMnKzZ4+({?w1aeNUu7YMhpjh_rnP;Ja2C+$E? z2LSs=gfBDZqjuJe_+wDK=~iQ^m|~yX+T@m9VQg_L$`BWA<#V)7I}-(eF|i%Q%=iKg_jPOSUPLbjL9jdKvSP9LJsSQ{++WAt0rRp#q3{pvQstxprW5&#n6rt0 zKIS~M)K-LFiK)8gfc+?@@Z%ZF7vebnC2pmqO!gnRufQ&qCBnq#fBiQAPl3sMG)OXH zDJHfCNi3@V4gUR&$rquaS*BP#$C!6kK+D&Enqq-=9y3Sj3Q^^-sAygY%;;%}d&DWw zT?pixVN5WM3A9B@yPBm$#jA-211y$XJK~?%TS2d`ZJ@Vr;HJH6mtG9*UTlWn78HX! z5(WqThLvYRqu-#6)y1H4N*J3DO)zs7r2L+ZD`6z?N?Vu?Mf_ZGsoOB%EoMoo+yupc z%Ys7HGq@dNG|{HlKqmcgseZGe(DR|~x#OVSeV|>U^@Qns7y9`K8WjzTRxiP1t;$MZ z*$ndm!mpAaI+ip?9ObzLU55QqX#08WQX%lJBF#3kV?Ix~1dHhkL~R0X3+KJi;xE&o z{qJKgK#PgW?^7uO`IHL&B$dytpg4nnK=PHvaKsfh9R3Rvb-W%dBvOuzyMv|yifVJi zhk&0;f-tBGildK$czzD9P6B27E1a+*uui0kPZNGL?%QadWB;Kk#@_|jp-YP9XlufQA=-moR?)J5F(wXb zO|jK5Q>-{94D15R@l!|R)(nK39zxoYz<$`~DzBy|%mvN?VezH|LlZu2qFBs{;#Rh? zQGvz^ZXo>Npt#@w;Pe0=-&1(+y0x}VXyhNEU!XA$q|slXk^e)K0QaGozsB!pn2vc9 zb{+P1gdK%Gg0PSA8-t%z1$G#Jx%mfShoesc?)JEaM=|9Ji1AANt|IJU^nuv7CC$l% zEkv*J$Vu7_SJ}EXGh;4MM+Q?hH^e3=hv^+@ifxaGgJ6M;a(@h946v$#3Cq>Vp@U(q zLFy=I*SpIj0|N-{OA3df)Oy;L+eC%YB>K=47aRfAA4?2LQpdo2rn8iP(}T`+er?g5 zA=+gfN#RGpU}1O&5#fmM(x(m~-KnP7?I;HQv1ITQ=zS~rb}Qlzhj-r=#5esMMmh|+ zaX(YcL7Tt|%|&p}3tj(EIdUDZBF${kHp0Py*#8Z6T&*COp9W8<0`vz{H62B7(9*@w z*IoD>i@Oqi41Px;&1+zPkD;>@a37EVY3L*HyOsF=huH@$DegMbinfnLA4S}c@IDp2 zEDgV!@b?M(8d`$Go1_tQmC_)ew2gDF*-viGk~Nkp5B|eikUs~Dn4-iW1}2(EDlmkZ z<<3saK`K`U`Oz;)qGP0-Y=g=l5`Lizhx23J7ZgU1lN;u99}ReaB60h|_=}7qTQI7H zLE)z7;;?WUvghA&@l6jhD&`=JX|{OUxiF@&4pvzB4#lVqasi{$$de(64{6+)gq^}D z_W|Z6K|JaQF0Oux=B7BRJSZs3RlxY5?ckQc+%jK`*2IOche7Zgq**m41Qve;tgqU# zkvk!QZW-9H8-!kefVmz|_yX$l4rV2t;s%Jm2D=pL%p)}DLvlb6V;Lon#~dX${(q;D zMFgGEVuJnf+Yi$z;$1-agE)4;+yV1F!f(bDG0ed(X;K6zY4dPQO)o<1Ct%Knh~9qv zyZ}}QySP#3uhGiWgZxdeu_V;Tl*OAH8|F-*_H~ns1~UaI3**sMDcnVL>&xS4#Mgqd zC})cHcp7JUdCqi$cE3cong+eiii}nJJw$(lDF&wo=Ej+h_6wt)mjEIWgC=ifP5ekm zA4y@Kh$uUM$>a)O23B_@%*lZXnOPN&xAECmn7HOZ`s!uS?vbP|r_3|RYi|bqz37Mk zN@3t{CP9Oj$&DoU40JUOdb$_qeEDI2j_`q4UqpWgT09e)R6mF?|BQP(^f2PwhTjhO zeHGIw?6&~#>o_Esb%a)j0B<~gN8tvoW-5N?Vha9$672H(RBA0SzzInQ62pj#?=jlA zQA4GYm~NvP4>VH3c67c?6ml=gLBuP#lOL zp`9}j_X{w~KoS({Bi1)8P|usqATm|?n{Z}_z{YbJj|LE-1H`u(N#8P2I64vc%|}Y< zroxzif%)xQadG0%Nuij-)j(sBvB0Fs0hl$nRrnM3WLeoMI&13__WfIwct>?Q-J3!_+}x z)Wy7=hPTF4d20xtgvoTucE3Ts~+@EEdd4F;=>f1sb&z|#WK&6rmU8kj#*4U zX-D`LN`VP&2Nxpqi8j3wznF>Ebl9tpp(no_1Nqx5UB&r)UP6YH=yw@$-z3gX&~{Jg z*Y&+L%+WSs|0Rz4LO0CCxar?cAi^+zwfOxIQ+-14e4oL(W@;@kKuv)qY=J^L6%N|3 z66BAjGtTOUgMTUb6-%6nHJE_ta|-se19LTvf(q@o`R(cK4pxvN*-f!YI}_1~)sOh5 z8VqnV9Y?h(aiP^f8wJ&o{S8>Gk^eL6R4iwKMHlP1c4UBu=` zZy4WHg7}+1GJ^ebLTNAW3yR#5{i>l64;npyQ(FLNzo2T2t@H?U~ydNrH8Rw?QH5doQ zsy`D|+9_rr3mJ&d7K2J?S2P2&vccDw3XEbM`t8^T+eC4ec2E5xU8zEU<%rFQnjk7W z7HYYz)Q^l+zj~Sy(SPU#D41$340bC0;C1>zUH+`Y6Q|nb%cciW%@Pw=-(IRS>5vAG zHm3U=!N@4lrfAkH$TUx1FDb_dQr80LcPR9CH|_+N#(Wd{f0`t(K!>93KIkcErwBV3 z%e80?;>%!?qcL4QFg0y|J_bXy&ScCnN_nrbS_=#?l>o(1OxO-?2Xe&xq(T-`i{jw; zvL+E&ac!z&FQ#$FfZ*GS{R&m-iTMRW+=vhx%P3oqNf2x|K}>Yzr7;<07C>{X1;(SV zXDYHUQ{-D2ZU0EC^`p}tNjVCV>Vo2LnbMAUfbbh30BvmSh9q}?Fzb)sg=Uuk>oSP& z4=imwO8!9{*nUi@7n*!r2*aQq8!BmM7`+IBy%j0-ADA>yxPpqDO1&nK?(U#)^LX+& zlIih%_}@f5?AS0;qt3N-$tX~gLyq# zjPnfAKZ5xYQ;?5;2pfj}E5vXE{*2z{P)tn?x)A;(Vc$moiSRMx(--pu!Y)Kp9=EeB zQ%Yu*u$|}s-j5r|4`M1g1(bA!W+w;58}DKoOxu^=%jN;?z+RcDIg{DIL#@oHw;XdT zR6R%gzYC51O*n%IzRt4F>sTL`8wFVjzf~UJnqE&}I{J5i8+C-HMly)0f7q`Rs{`9v zomk?z8QOgmxW5PP{m||nuz~}Dbfy5-u&&J_@Xf{Ch#kW?oK9I;`_t@%b!;0#L$;2t zit#UO6x_q=Q-9_WuRyoo1Vk@b%870OHzRH_QR^ot$29^?+Tp(oF%U|{bU@-AM@VwfPJZ1^0Nr#U*YPX( zse$`5>FN;VM`_&;iDt_=h1~SLa&fpzKC(}gsqy1~NSh5O+p-Z~FnOdsGx`m|cZJK1 z*v}LyUZkL2Qf{6|T``U{Ghqvna{t8o;hV05m@384NY)Rg*O?LU1$m%4BFTLLW;HHxngI7c zxL?NYBu(63#eO&qt^wdN2uN#y!V9RN22pf82J1<7J_K|-@Vf&4UHs;y;Uh42Bzytk zcVgG*+7`H9A|DZ$7;On2_LfxYJVna9Gxa)ul=4;Kc782c2+CieL=7Jf3Zpb=(g;57@Xeu(`Ousb03u6hDg=U)d$PP&N&VWB&pRgWK05IqLiqSXp3Oes9Mkxr)w2xglD#y9qS2J2dkIc1L@-_r?7RZf&Y*K;Hw}6WxjaqpDq(7! zpuab0-y88$TGz&;-xIsm_@w>}Ec%>(3x8W4gzt}W2DO;XbZRUq^5E;qsTCPcMIO$GTpWE5rJh0DaBtXvO2+eVP@iLp^nE8q#hOCLj#h=df(EJ5n2X zM1T##>yS*JPreWtLVwAB_Ms~1Y&^Ov@YN5hpzRXfV95^F^=UgTFwsAeRvXf{+|r&F zMQ^furS{J%ALjcER4^O-H%=>F*O&0TTEp~J=y)pssd!gTC70-n;AI* z|13y=pC|M;>?`&Akr>@Z^_j+in|@{(UNT~}e328zS2yx$6x_&v{93dfHZs69M7NkA zw8|_cQIcNkOL0DAqxqS|K*N!2eHL_0%90Ra%Sx{66G!VH?~M#_Eg*GGXm>4S`?&|P z-bLl?8pQg>=IWM3M5i>;>q~-N-#FWt|7QaOt4q6|jZ&}wQmifm1kWuh?I^znWpw;T z?cEH$ZV{{kBu8m~gp~0dGtl(8ZaUpa?#(E(uh&Yc8{lE3@U`us{3!1Rr#=IC^lkh} z<_3xX&lEwe4uLB!LXT5fd+POx{)*t!H#d!spO*7pi1#g~OcO}&J}=9{$=JDs#tf31 zU81Veu2aICbis?x4vJAVW=|H7lQ^;`yUEGAVAlQ-=K@E6^PT9ay*dAQ9=mUM2KgIb zRHmK@*54%*tZwA_ICWW7**p6>*DJf=u0o~wS#A6Zq|Yx4zNz-j##vi-O@Y_@Il#0s z>k^NGjULqu8c=CO!~3IWLceu8xoJ5!8ZC?gPZ&CHeS0G9air%E&R=n~r}7SVk_VD+ z1Lc@%D?D|=ox`h5mfP1!7Z|v*V|rY4#QsUri;qiGry9B&^KTf(yPK1rhxchrF+d%l zZK-pB!hd7R*IQv(dt+{&J{vD<_u(>qehL>oH(3-Zd7X<1En3LRRC7Q z!5g@>A`GA9ME7f)*gqAwI-@_6L#0{5Ae4R$pnqn9D%C|dfv zASl!Kqsu5`)QyveZRifT&C;ce`LjT3Xt^hUFx(`L7vB=cUEL=@QDlywOm~2fA)K;* ziE;=(9`{&FO8E77^kykAW68EdqiM+N3EKJ%+WN#4rjlpVWx##rtqxy&F>?wf{VjFc z4f7pz7LVDwHHwo4wQPvrYMy{*|?@_T)2GzH<#64Yb#P zkd9i;7p^mBiXN=4lH!xe{QJe?@n^sQ=YV?P*PTaAQf&NfDGWHtgGTu)S`FPN%~%8^ ztxs0IOxRq^1JbY(bH!g*JUx*5yXfwR8(s0A$LPOD8YWmG9;d=E`T%AJN!;Q{B#J($ zgmI8RAH8nD2eoYzdqNzEeakHq)5Qya=Ushodb^4km5t8kW3tNM2)K<-M`BB^>ql~1 zhJMERhm}(~TmAU?uTKUSy+l$@HX6i&5p>HaZKq-$kEg zc?RGoiZhHdY^(WiRG6x=wr_83j~*+RU4Ae?3yRT8?jfn9Myc(i2#y&=e@vWz3C^+m zcDa$T|KRpG_vViH>;6C(s8jNH_{C3GijPbEWys&9!~YO8WeI?^igDgHb*9#J=>Ir zxqSZNSWLoF&!ZtG?ja29e}RSKHku$Ll~efZyI)jbRj>3EdQio9tf@5q!8P!%zdCzp z@8IT)*PK1F51V-TyMvZm ziH%M?(U^S@OcyOO=JwlvWAn-@0a9&{U$)FtSX-zjDRsP~tuZIONgZy+JskZr^klT~ z=V`7Vcl5MSH?y+TQNl>y`RMO51!IQM#Zbix1IuEn3RcxcfSPh{_u_cYt;W4W zGd_r8vqX0yZL*22ojTP-Lxvdht#9$Q2q_sYK&e&(gm?6R^@+PA1*XoD;mhYuAJEY4 z{k`crK7-r-$_MHm%4cu`DlU&-pP^gB6m$GhS?Z5uTMKh)r2Fg{*?ea6Z z5PCb(K|dRRCBOua>+jchkUdiZx;QP0Cm>HK-prP?F+YWSe{cFc4oLGjCjoMAYs{!i zmr27lJ%D(M)27+Dx_Xhl^wM#5#~q)yKmPH%_PzJ!*x&u`--ItJB-6>6SN3z$$O&RV zFt1APD@->o>e;Ih-n_r;RjwR5+gE4T5f`@+0~~jM+{MEb^ShU8hAzcl<#bBv5+-Vc zqztA<|NgZTMc5BM$gR=*-*e9k_RC)$Z-)(g)4u%jT>IE#uTu6Ywdx>Iv>;`G<6weG z=LIMC3NN^~OmdNQ>y*czH&gGG@az3$`ndH>;6IL*#F$9F5^2}1@{Fk^HbGonsNWN4 z%_=s3Nvr1TSPvJOYB%MtC_b#7T?06Ie9TD$M<>SgQ9E|;*RO_|gSA&*9qW2|``mMH z*`Y%pw|njNs(s{ zaDcyC3jeY+Mizc4(OR~&Bm;EyLCW1Pt*4iAv-R{iX;{rw`TH;r-%HV2PC;ql98}(; zhw0wEd%^%e_<)y+rgI~ku9q|`&}ws;Q_zbGUAoA*5pqj(KX3HR*_CiH)B3bc9Ii`PiF(O zH*omGojN79U3Y~WG9iEMHC<}EqZu(GG7~5ASi_`t=FK zJHY<{|LV9M(MSrPD4{ilcaoOc#KmFF??>{){Jl(+UmHtYpZ{OC#>raX6IA78@c3g) zNoJo0kHVeDvBMX%tIe;);PEC*RPu^p=gwSSHzVZOz zdz-Oi+nBj?IqTIWFzwp;<;5g_-NxUwi-CH*tQnv^0X<10(`4oXw4|%%5YVc?YXtSg zBbYkJGCECw|1Qp2oi621m>?$7+U&izxJoULv#_$fWSZ&Pb)ngH*F0nC8!mpNyG~c3 z62_-1jP%N!8-o2vzKT?JK|YP9x-h*$V~o$o))5`4`|nSVI}9GICDdAfGjAT_{)*Xf z(&=Wy^gl0PiG?B2OpG49BO@u?4h}`9- z2pJnUK^nBvdUIw8=v@fteM;i|Xn2l+r0)QK^3=>#fHa3=X(I>rKcLkKd&-Tf#{hW% z?bOXT*V>59@3F^?8lh_vDe}7tWx844Kjx@8lb~7HqkWRd&zTZjfJuEHx zHsIuIsomo#b*^N%a2WR&Nb@Cy$}V`gCH+aIVtm2?TemjdIg#|LHs}z#Yp=h45$(UM z$l%!1MZE?()@zB64wK@+NhWQ!g<=@*DI2##%phM?p?1#p#OdI?_iq;zYKQPmc zi-akzqR5VbNOKni^A+Oh6?k>RTUSowB*37oZ1Ql8o(Xdk5!Kj{BWKuau6fUX|NA_< zYFG}?JJJH&? zExfOP!`nIpt9l%A$ZUJnRg>*ymrdYc^b7cXY(@AAD=S-aCs{-I^%$Tps+V3qk_U|N zN9doWfu%Z>BGPmk_6Z-3~Szh(jn*cIG5dFhk5oFznEfw{p+{wzWa&> zK5+D_!_u64Wr5_!0|5dJZKBm-CVcCcIH*H-Yr)i;DM za6S_K7W#boql8x{iH>u}c!uN>Q_`=)yJI-5Sb`$`67&ePo-cIuaS-Z`z@*M~YOD_i zs2PWFDLkHl*1&rbIGiGPT3d%l?4nmsyE0W?(Qm2Tq!AuZLg%65YY3B4x=oya%}s%a zC~e0mTK+&77Uy%OdRp(^1=?FQumUX_sGfoJKLg3{Lzv&3D5`nUn4k0{9eMq4F~F*- zs_@=>?~TTd8^^a_HkX>7cO+LY;{HQFhY)g8h8VeQ>{!0#iZ7 zyfhkW0a&KAXMDaQ+B{*M!#(wXh+!Ush%}whl;kPCJQ*WTV|G=%GCo0m%9tU0l$RG< z&6yJiy?WL1B}F^asnh1}TiS^eKV&y}sVOh#n0;+4(o9{@k96CyLE*sIus2iEF9oKY z{?GFXdjBKOt5TueiExMwW!Ve}<4I|$`w~g(IjEpFfvSUQ(SzTuojv4#Es;3r@9Z|# zs_XMFX|v6L|2sb|dmE-f zbM=Km8E-Btf8kAe=jf#8fotXS7{1>5pzF0R1Qb5l)>N2RrRlYkKw47}<_*!RK>|VK(-1=%6iY+?Jz%0+*%b`Sfq#0{FA8 z0+?R-#V>v_H+1MwcE#Uoi2K}_F=N6VcG#h~%PzYVz;V(aTewyt0*#z{k5-MZ(Q|of zK0}a*qXMlFw#Vk|iuI4$d}Bb={;=M;ihb}CgV==mphjWAxh_q+A~hw|T%@p6x%{xp z6l@Wun5&3xRa)_Xh+idUEAHXoXgZ5zb|O;TYc@JhQ{X2RE|s0?CO&KJm%QCUYn6Ys z%bu3PZwi-!VP<@zDF+Flj%Bd+`2_*0Bhe^7$pMb@wYq(vr7-!8m##%gw-@zEKzb=F!E} z?$=U(-|AG39YIWOh*9XMrhbU66(h zR%~r6=Clf;S3WW|4=yU^|A;hsIisegs>oM%2Jnt#RDD5il+Pz|Id|1+l=))uz0xS5 z4Gw=tpX?^;bR3ypL6rM=1N9t526!pH&vfmXS_`^=q?zTfU-q;N>P#9HHE^7QF`CDT zqaKV2(@o8{k>~DvK05UHzGl;*?QPj^;F%k10ufcAXyRqdY+>^Ju=?q7!Sp*$$v?B8 zM_BRP;-Ij_VnFhM`xx3zFr`ZQB+hmEs}A1M_W_pb2ebMvl7u8)r?3y@oZz!vyLLTs zv&}Yt`#Q%^cAh$QY9|8KDLJ6rWhr6rEnmKT9R9bJmzRg&?K#4b>O?@n??`{H?5I*c ziBrFyng;4XZ6G$7nHYTqrh(~g2ctWNfo7kIii)k_4~}-xqr9a zc6*34OOY7kqmMpX2jtdazik}R;=WGN8AqXiExq#T)?w~~t)kZR=~1{7Juqr-_lUUY zGvn7KDzn|Ain~n|jLjvZup}`Zj+6S-oIL@v@^A8RO!7&aLXrA%+}d)MEgQZ|;x4U~ z38Uhkj$y5?J%RP3lSN=F8#XzarhNR^Yxz*y`*|J|f1JRIV;ao-MrzqI`u*Z368N zBbkmlxEA(Q~Kl&v6y=rMZ`g}iEW-7&r z$#r?>op*k^V#SJ&5rj&LfyQz|f^VjO`Di+$=FOY;kL$0$UV>Mf2OfC9&1%+1#ns4( zL=%P=w~KPM-6A$z>5zv;kSa0O#F_oAf?%~HEUnFfrnHaK^ujTe40Ef`2(vkT4(ysDNo_@cH-7oe z+)*r0va!=?q@%+(Nynlook{=WO}D_n5F1;u^#*(V-6cyNqRSiI(%YYMNt@`^a2 zK859z(iI;+_Sj>>dpj8EdSQZUXjNeIj#1eS9l5iP4pqD}+NTglx*=JA$4rns>gh9_ z|C`Z~@t#qaS~0?{!q)FFZQhHGf)2oHf&t#Ej^a8CrU+MguSyfFa3@~N-l$3>!sH@^ zMk9PiJ@4hFYl^nX!?^?YToj!=g`+)oVW*R8&o}KgMYgQKCK5Ndb2)bxC*3&vxXyJN><%Ygn|TEnZZ zx=Og}3iG=q#dPuU#~&{=l(dQC2PJ31IOF;A=hr9=OmN;EcigdmFLBy29ri*&iy@?< z9|XkKN!f@IdoLE3lK9jS7XLjv6`OH?WCZ8#Hq7^pd zjIy$4zd$+&sA)-mknqo)s-p88Vz$(X*JD9+;)^!;o?W>4DYajwqQ`vqEPEICEJ0az zbCa{1l~vfj-L`aubIsMiw!gaiYG?o3W@;x5MrW{XdBhpOYRz#Ot#h@sTAvJ1d25Zd zKa$@>Onc&qC)OhEl{g;~hJ_hAH*0%byH@0D4R?`?^Sk?h?z^> z26(!i9C2ejQr~QtpKeWlH#%A?@P&|_%r0RoEgt)w-Au_f9e@g=$(-3DS*mqdl#3-n zNOH@v_xENP&b^#u5RRr68;*L|3>A)Aza1WRfu!tz8gpvl-2Qtw$?u*&{85DAh=e$n z>6slkaG-Pl=cSkI%V_zVeqHUe(V=AIckHppS~{!q6Z7*b%LFgG>@qhUrh~o=oZfK;RdD@nIR|odNItm<+S7P}FUWtiHL#cpZ#aWEI23LwU~U7JEnD`UaQgi7 z&pRCMw9`(GK7ae$-`crz=UO(LoV#PkjtSj@h#hy_(b>;DlXdRAJ}I>Jt+(F#p~W$n--5Wy28Y=r)bS!-u=m?lDjXt!b1G^ zWv`>m(MO&m^PPfGW=|WFs!LF&5l_=#m8;>1hhNc8j|<$aTZDMx#oEH;?gVaGj=5!p z%_2bYsT}E1v0(T#&gb*~11^Jm4cEu;*v^(gd+-&RXU$CeTaE;(Zp&(GOm%u|L>nG| z9ARpvFC1L8YinxE3ba#kYjc%(C0cHl#chL%0XKwS-4FlbFf&2xPX7xkK?)mh?c29M zLKS987BHRVO^%{=*&Pn(q+GnQa-UMT;z^xw!U>9OSs&b)ontM`D1KP|$+V%7bq9Sw%N3j{KE@FXr`3+KFZO4Pl@PVB>EX(MvB1>s z&_0(NfL9{~M8brj!R1WYv!@A{E)9Y!uQb7;MKYHL%a<4M=ee0UaLfRjl$qL<)8}7} zH$$S^%#`BT+yMjInzBJ0s5SSQ_aUBS_8P$KIEvva$_^S2Ma?&}c^|;5Fb&(4zW6sY zJ$S>+gMiA+mO(qUW9zAA=>~^?Ghg=xrf9Cn)OUwTlP2|7*dBZA;pougPku-k8dYUo zNoioAvvGq@kKpplFL#kRCi7JyYS0y|4DHcW&Yg75)~$IvqQ*%=!317goj8$O)Xegr zx;hBC4&Rj{iSMq&SK19mv6-a$RUmmg-tp#}Z^nQ5%U=R^-?`OkNvWQHe&8;7WGp@& z8&VEe?b@~5jw$m%B*)_T@#7l|KAD6KtRB$~yg$*YhP9iTicM>5v7cgZha{)=r8hYx zMkgj{(aTd^gz?EOBpx5Ds z4qb-tBEv`;Fr7%#ItkE!C)*&*_GW;-EBT91Sy-2x#^~x znw@vvnd`xJHJqGxb?0b@>#%b8F8#XufFw3;Y$PVI$wB53TeS+@EA0lNM1}z!NA(Vd z4sH;x^rYo4Q;MmZGjVT&g(cXgRG~ z!H<7D8^4&ZgIqHL-_vnZus(nO;3r^*S4iuCWbFnXcG^pL_xM~@%_Ie~3FV^Pb=O@1 z3{@Xbv|)YF@eU+<2X9p{eAt>!ca(u`Z{l|Ijb-yzkDpfHmkn4iRpp0NWxYNOih$6K#5bkm=s~VW3=WSPA5B16K}@ z;HInU$(cUg;k=I8lbB>E;qbbvYb*boM|j9YZuy7yH@Y#XYQ3?ftsuURFc z(6pDSfFlqwKXq)rS};HS;SUX`>s@sDOL=6=%=0OKtMmNd`4JLaotv8G3Kh-in3?pe z#GwZ8E*zG5_g!PY_(kpn-;s~8M@?Yh>TbUGy)DgNd(ATc`q!pNh{?_18){Rq`5`>5 z5?3uz_cdjJDtNG%Ww=fzTsYt4d(Y=vf5#HjQrEDIw>A42-P|rdL!MJPWI(0>sVgx8 zvy7X6kzX_O9J^W?6xsQ5s!`72p{lk~^*tIWV68 zPcvtQR;n)9@TEVYK=cLQik zcvx9;O=Toy5G*v+3oC-;M%_2feEjMp5=nt#f(ZQSUt+JuRbK^6i&xmk`Q5^lFPRb4 zyf4`3`g{@(ZMtdnl)CJ)fVC18BWDgftPI2~BCM^s_+rss(T$)@tX(Vl=T_~Kye|=a z7%Y9CHPsbeP3!R6rhm-0)vit=5)p@gl%~8|Smt&O=jL-WXX=OBU7gy|@QES}a79o+ zCNYbzGDCx^&%nr>ly05=I{IQUUG=uN-g;}qu?v@Bmbv+krvTX{>B;n+)ptnFv$?dm z-^Q}kIU3+mGZ!@hds9yBy|-vO&-&peW|v(S!vt%Q0FyynGw3#LR_X#N*w3$j0e-jx zaR>G9MND0Vd-H`ae8EwpaO?5uYsfJe`m&a_x;bXYwAt;xGu?Gjj^Ijl;%M+%&Zl(Z z9ok4qsoO(o+FVlxD2QsE6$@>CoE(>%SHq9ZQZnW=G>otUQ_`MznM|LN?CoG$S@oEi z=32UgDgW<^g6oKizEbP+#s}wrev~q{#~y3!tXZ?-&Yho$ciL$N=7%xM0r9uL-Hvp6 zk4E2kreaYxv(LSKY<#xq7am=B@@>}tUf06H5vDJ-O~eFsPJcXO1~2f;oH^5tl&ZG; z9b&M*WU9@#Odq?0=?t!G%?!It?O5I0v=i}2Z|F+&6-}Eq?TrBg28d~YnWvwAT7Hhf z$&}LafnQV7z@iN%byCj^Wk=$(KmF-XLoGkBp7j*TM5PXvV0eXKZnoUgTAIZC=tn`k z%{C_f{qJ9lyLO$3p3bLtbK-veI>i@WxZLUunXX+Uj>qVo;0;T0R7kj~nLd5`c-3tC z?Y9rV^{sDN+5+hq@i$Pi|1l=W1TSOct8M-P+|1^^DDw@u6dt-^6wTdU%nmY_TjbH zhT#h@%*DPe$C4LoV7yhdfpumoWKT0+6n+*=w@dyc3HvK%%cv?C;5te>h(qW9N{SBZ zj}wS!+V}Ds}pJ@#4i}RX4sj zWpnLAU21?dmY_j@M~QUaj3X8-wcKi0z&9M)bxhDp+PB%Q};Tywa zTvHRc(VuB;?w)%-2+u#i0(u>c-XZ+SPr8Tq+_NC8tzF>a)YOES?uKdCw!mB`3gCPf z-*TUWa)!_G4({}OlG|M-!O%i|_p8cjvrb9_wWL1I`DtK(GS5BtoO$xeCwW0{sN3Xw z`|YCpGE-We29P}HRm`11<>s$ue;y8L&7^lB=Lers#lsFJ_td;Ne;;^E3Vin-?L}uJm)C$y?VuYj$-AHKYq*Fii)l^ngy;{ z5iVO9Rtf0m)*>c#yxBb5KK{$t#b(*Rf}lJ&Z}Gt5q10&X%P*RLKKhtj+3U^#vsuhp zwMEIF=JK20#MQ?g*O|l6JLY)<7+usy{`upMtH@t{b>C{mnKi4htfHc< zn#2u%U^#$mZ`_bvZv0n_xwAJQSVpe;G>pDfiqDee-sJX{){o^5`szi|P7Cid``Klk zhu2fm!k@=nyLWoq1kFFddF)K#4Xb*q_c`*C+T!|2RDjK|W4d_mslAWrUill9_l%M+TAGzyJO3 zTb2*#3r`=00B~NF#O0>|A8Ux0p ze`93PBW<`1CcPBF?j3b4{3Y7IFflrs>#&ZG7eyz<)1q$`pNRTj1F*Vom__`y{bhm)SQRIVrU58bCCc7 zURp*6v+?q(!E~RRw57t{-q7@6e~xK34(jvos|M#~qt^H8O8o1h!ACBPPP#h|xQ0xO zkc%ej-^6oz;&0BfT%0;%M)>VBxQFpMF0Wla(6r~&nNuxJK;-J`u^uHrS7+$=N$6Nh zCg1GYv*-Q(BYfPB8FB-$yP3qhW%A|0`q%j&8t7xbOF&lS^cw!p=ub78X*sU=&o&7?u zV#PPx1iAg@+1kEyf?93ue^9t!SwGwQBfdv{X^N@-du_4!1pZ4Qpw}=vQL#Rl+RjwD zM0lSIm8_c#f;(4fU9=^Xh^L6iwoOeGzTM4~-88dUQ;FYZdFXpJrJ9C1XaaRVfJ#cgW?U%bptqz`T3J-9d#`&x|fB%MfA|OdNEvZ~x z!~mh6L!p5xGIxG0Pzay*VDkMp9q$UvACqr4E|J(5MtCr1)plW>YIFR=-11zOu#nFT zCvnu`pX{}`8BuhmBw>sDtOu+4Fg?JKJok+Y&Q>=c-M6Exdhy6T=NB^Gp zai&w|tARI_iNJkH*K@l-cbTI7QD8>Tgi*4ixK`=gxAEOb(8qEfZ~$w0?+zO_>Tetv1?9`Dz4(FevL-=&1^PUR>l1 zWE0GLwMKNxf4=IMz<3eqTs0uf?`jpS$_%dFr}*H5SRX@gadYk~oAqGdcNtHDyd2J1 zbb0>ukMLiJ4tYJU_0h%W9|Y$w!6dZ>cRl_(OcC_f<^mU8uk>0O{|VTy*OmU67( z&n(10$kccXHaNP%5G!=`5*s87@#i3mS;QxY5uaj}e?zl(GKgP{pQgcLfTGzN^Gf z<9@T4e;>Q;?YQFV9qt9Zk`$vayzs(q>=btZ_d2e$yt1-VyhEA|&?{Q83#?^{%aPpw z%;5DM+TkX`oKS|j&Xr?CRA3BRmF%77D1T|KDM@QhB)i_>{pF{^N^yxvI2lbtlvD#E zano6sCoz=H=hKuaK!nw;0csRet0{DV!tmlWK^^)ujQ^33`YtkEdG)c`g1VgEsvkH}XImovSJEZ0PwX)|>=0 zOjMG$qi2sZnXA&C_9?q#njVxl9d5wa{|RS!_D=&Mt>r!Z@WZXx;AoV0j2SbgBHirp zdFYteMDz#15YD8$nYdQ*+w6!9V5ssMf0_DF0cCtU-1j6*RV*vH3BK1@ol=E0GI-_E z0A~aqqtS-~>jBIREFqCE|IPJXa=EYWU~MhPLC`{RDuGU%m~zX`FM05caPeIj>XFlp zlhD?_Bq%1QX#oWDQ)+oOWxVLE;{4kNnX$Dhg`c`4On7Xova)||7snm8VDu$4e+Ycp z6}QgLQXO}*6pJ6dZ_L*xH&S+o7aIIZB-5)^dp%5nJ>A_*?M{VQ`eB0e`gp`GTu6H*oOvxj zbwtr~88kcta~iOA#gxk6QGJBWP`uYUg~F#pGQC@w8Hs-mN@FHHX% zZ*J|D%k8`!PjZw|99MS5^Zl=`?=hzc;>Cr+)s+3N#Y7Q)9Iat`e02w3f75Z_1HO*K z%+jxp9C^Gu+#?n0P_$+DxG~;wJn1RFIT~1FFB?%^-gOT$d_s(!w`6g>M zrzu=zt|IO(iIkw*fL}|>9~X7=FSmVLm0OyNZ++RB*{TkUM>h>dL1ZR!0f75Ds|JALP!xsa| z`NAXpV*eCCibr*3cO!Hsn(g0e-&`(c=(a5WTdvbjXh2_yh%N|E>9;V|nDcuB3pm!d z{pyyj(fB306)i`VhgoyV);?mkZzO(6{|UocXRGY$Un+aMc1gK?ALKGu`kZA?KyiwT z%F6z!qcxP|%9 z0JAw<`W#GcfK15vV_hvJn2ab!_xt}6b8t#P@=uZSmdZ(Gtea-SDnPWFfE|~qiJme= z-vDy3h$a7GQG7|mPTaQ2O;-kS&B@7K`EF&d31?iSFcW_Cf1!zHUYFPhylJA2e{%My z<1^TwOznR)VaNZb_8Uz&;DaRo%u57I1v24?b0DDQ$iwII1% zWY74trY$M6E|cwKB}wf1pZb@|xs)%SRc_x0b=kPSe|1jEACz&fn^=xERes@JbSm2F z4DFte`3SR@S2O-tS4#<|c!ywK4op2#|BV!act@$NQ@@pr)#^A9%vXS*brH?GPfN{T z$waLdtRzY4*L64u=vI7IKtNhdmMZY&$KJ;Lu>*E0le7tZ3!>lPD)mFme z5@7xsa|rFV8z%l0eagyu?v7o{B8pQKgOKDW8c@5x`l{gz2xGs?bIzh)?y?m;NYrAw ze;c8F`Q^Pa-e*m+)kF`HH^6;Y82u%?Xxy4}OYp!98;ef}) ziPJEb*=w(TpL8oUrG&sW&6p_!w;F05W+bgsL@FVpzTK@mxQL8~rAA5CUXt|n<7eW8 zm2c>QA*8-P*s@ba@J=BC3Xbyc!J5v*&5@faTS0`sbgAVX39cbnw%9)V>~#F+fARX2 zt<}^%iWaFs+d3dDF@TeMWd}dJ7Urg_*^w@d_Nk0bfV&VarmB3jzUU+*oa(siO1XAl z$5UXJ2ekVo>L9;MJ?66-x_qx#>UF(i3;b zG7!i1;fM7sKZA* zcX=QMiZFkS=2WJA3#qEVF=rw~xp3GTy)iBIW}C?#bFbPQZiX%Jku>ri$AM{ zJb%+`!c<-Siz!8wJ49AZe`H^FzW-#p9+<};UtmU#tmZc0g@kU!Wu_t_L3PTN8ymi6 ze{gcB@Q;2CJUx^|agvK_>f@%~%{DZJkYu?SxR)?0{{}+cANa@P_xol;`5Z(=Er|O69uER{(eJx=7 z_6+dfjY#dm&kqD6Rh|aDdv(k#?~w3x5wv>_yh-OeHFzvr9biqxbu0&qB5n}9aGB>`#{!{r^&fVIE>eKq~m+LvC^ZQLu+ z*75=*XJbVJpDhV63=;_uI)zhPd&Cj7Ooays8qO;Rumr+gasCdl;Z#vF2p;nlO& zJZMvNJ7^5Kt3&n;@V-4-gO5~y zI-k}(yb`XY%E9m6m|6%{-Q+JV`?eY}F{R!D)~=ZUhgSPJMO*khz1Qd66fJog7R^0N zy|+dC<%I20oHWFhr){l8Hw+V9Z;#epb}vE0+hY9#+T)QUPqD|N(n=a4ZgOhjf8>)R zbKZFs+-w)QlFp32iY8ZVVl zs|5e`tCvi5J>#o#jFk%mPBu~D57lXA$yXfyE00y34;`Je)dXF2x`!2Yr zp*`+L(!mDrXAtLPv?o>#L>rP)htPVH zX!uI%`xUHn&v%2(<$%xXX@vLCplevrT(jJxJ)Ua#Moy^y-5l|{=Q>sE$KAeTI7^c z1$-sGK|0SEOa6pw-9%}``K1)*2viQz+{qO9FS4V`qUhaIG=1pQDF&Ig_LyU8Yu|Wd zLG7|-3u`A#c(3+re_unCGD~X_!K*dZ%;jcXPCA%G{WU-^5fq{k$J%hjTe_Q&&)KwC z3Wl=N;VNG*{F`I1+d&qrtfJS5QT1T`=S>|UAgAgrwkXy<_Sk~_;>8PUXU?2mTUGTa zWuKbw*3B+Y*8s#0)^`o$_l)2@9?XU5L|gCf2T0#kJ$nLO3dE!M5#_b z%>jfR;e3-Hg()vh<#r;kDZt$?394h9se5f2KF^j|1KfgoO`_f>XDZR8{EL}`3E9<>;8r~}mt6$a<2k!zixWxG}W0us; zn>W99;llZ~e`Cfx1r5Ab%kqw+U)BIWH0FYBNeiP`pS!@W|A2xIe<<4IMtjO`^A}vO zqISxZ`L6#x`|RsnMEnHh&)4ulLNN>ri*mX9_oVC6wPfKg&DDknVv@e^_c=Gs9;(faDTpq)M4#_u(#} zfjb-4lwIkvVM2~qNQ2h7qiD+TbPYla#bP9>PL2=A?fn%m7tZ@NK%${0`I-U-*TF`^ zkK?Gyv&m(xx}0_7spxOI&w+?+qY)?B+=#uEIsV7#8_C2z;&9R(=Irjf$q|m>C@%gd zI{&f}f4h-h4GGgcZF_wYGRIt?sgS+~=@v%$>0-<&6HLplwZCcT6)^UsJ}NtF=zk&| z^|rz@jIewlrj+m`0`V$~H}5C2p?}t$wP(}F?%TCH_UvHRFy7j7w*&+9_0_=LKP|X$ zxHj?V^ALF1QyQ-5&FT;PrA}?mI`DhR8lcw6e?0wa^W#JG0{1mjG60GObcw-5DzE4u zTh{I-%`k14jbL<6okLet_wRy=_`zPh)as@z&7Y zFf2{J$}}n7T~v6-s#P}fs$x<>m+3ocBvne&39o#({564-+bh{U5)|gus?zu=pM>cN ze`uwL;V*nWjyu8GiOD6 zM*sl1pUi;t+k`e5tW4o?Q*3*eiHaQSTyY&9&(r_Ma7<5Any1jWz*Aqt-{LTa?+i-wv75cM3Xmi0ymt%@2@x!BLJuX33+ZL%pn@<;+Uo#uOfelk6m8 zP4L@P8bG^t6KuxrqYOpf5Bt* zRBp_|x(l8DWhAG?Hw8f%>$ink9{CFT_Yb&AKV;dXqwU{6f|eIjPE8_nIX1^W;$nQ2 zS@~r1#o3}t_~0P_9FCS$+)n>1(Eq}|edD0Ix|N+Vqdm(wK`?LLVjeIm(EoJC*(@p) z=FcJ>8)&^~Rf8HT{iFd40HJ1SjhyZF7l9?qCoKDGKnKz~yi?4XA zN+V>5XwkY7s4ADwe?iP>I9ek}pijyA^fBh7lZ+WZKKYage}9fnNDjv{eH~|Z%}ZA1 z?z7+3mW_Beifhj0Z7(FaHv5}!*$+)I&j=j|rV=x1gltpD91lo7@nvv2v?h+bTtzt?4T#N6 znq&p}HoFs(GcR5%v-CS0bL; zvdY0_vHdY4?Ki>EVL@@!{mIU7l^RY7+{VVrcD`3niLt3TN|RM-e>}Vb{(#6>|CJFd7YS9e3t6CPI6j%Kt=4@cv+X5AZxF*^&w^P zowe7Gwb9jF3^c@HLw9rGtkrnF5b99bP9=Z2eRwug`H8T7m^T&2!|{H)=wLeqG*AuW zDOhQwo61*lnx(P~O$pev@C((=O5ehfJgz)WeD+yfxH#;5e|?kP%&(>qd>dr(m9;CH zhw1Z>pX^!K@>^GGT{FO@>f-C&SiKsCdE8`i;{CP;K(`c#_w(@tC#&S|!?OPW3-)Z? zW%tVC+WYCNrx67D&DMtLh;r}oBq zDl4n|hWfRYvGJ(&wz%{(-WVw#XP6F-{4<&G73u{Uf8%)Ze)df}2~u6IdSBdqCvRGg z|3bA_d)rMr7&xCt-v=A`Navhm7fH{tNEmT%)Ct@-72kf@p2q~PF<#h#XTRe-@X+k{ z9m*J3clo@cpt6@lU>N1V7io_(c!TWX%>dCljpUd}_* zo+m_Sf5PeZ8Dtsvz?P;ohPG>sSUX0)V(nnH5BuBNrlZS2lUTgwg@H8IWJ`zBcA_#_ zwaLVWO~cnw_`TQ9`>57nJMpwA7HwUOF*o9nPA+};itK`omtx$Sb&#*1*e~N8eVdvvV z8@Dv2`Zc~;DDu!=w4#qV+9$>w&6vkyPbu0J)>PEKSC=u4 ze{K6vzTo5yZU}2a+b9xzHD?ts@V&MhIRkwa3I7A~BF;2;!tKx<+v1b$Z;n4XNvwFl)g{9`B{$s7RKPA6A9;wupdI_Tkf{^!xq^Z zVsAyp_Mjf}{XX~}COgRqf202}Fhjoy z_8_Ex2Y!<;j@O}YR+nh|0lsB5E7^sL|egO`5%ymW6(a0mz!O2IvpI7*2 zpqrPOh3$LZK+G4gk3@f&e=)m@wjmKSh-KtqK65t@Cp%a4g|H9xSX|6~4*KtGf~TO1 zq1avsZ!7#`@$Z5c*~VGlT=d^7wIP2^vcACD_ptUo@JPMZ@BWf7f>n1LE9kYgaoToc zyA%Ba-c|TEu=2i59qEpD4g62o%Y@x*#O26mVd+jb%(14!-W|%len zHRw+0>fVK~3-C_I{{->Ni(tX+(`sJCH>_aJ&w|Bu#RT>u{|F{)k`P$$H3V$n%(HP!`>}E9Bx#ct-*E7b0gAIfqN02 z9k0-a9)-8VUR8Lue|8kHUq(v9(C-K5@5266^9gtldAJJsIQ%bI5~EjDXyY-rJ(H+$ zegZe->##S)^EcS-A7MxJV~2Yf^Ebvqjf!b)>ksU86yd*Q?k;lX;XcHp$e(IMt_9}7 z%UJ6-)&!pr^)qbo^TIBwwm+gkJZt=rKKhS9#0ou; z3Gifr_J?49H2rtjmHb@5bKx~Mz-g8O5}W72aSt*Pn7jgeWDK$Z5+2|gSlaet_$dY# z#g}xlGmb+%f2R()z*(T?;vet?R140&QX}JReMdby7k7-Uur(ZHj0Y7jtzIac9$c+8 z$%Rkb`aK8S{eAVFm=pCuwxwD}`ga8n!du|K!Pfp-`khzQx%*O3BjXx#=&Q6Q^E`~l zwtmH3vOTZc(0i!c$ccFpzqDq7=Sc8<5?f07K|Te)e+-*vPtfY)pjT2Ftg_{QP7{@m zaD_`fPu^Y@sMx5@Z1CrbxU>V&v=G~sT^da>ti<<4bXgLF82>T!=V;$T-0jA~s~Pho z{7$TgNk=SZQ;*3c!*iuMvp@B4A9&}e%#RmFR@Dp4;E98xIT)k8|lJmu13** zI(Yw@f8ugMi~|j7LsO2ExQI!dAv-bgaq)W`o&YBAK^l_|7GUy8I1WC~gU{z_dx}Ny zY@}rECyc3E&v3?Yu--u2ZSYF^df7G$p9h@C3u;r5wO7uAIfy*uH7hWIQ{*ea%Io<7 zoQsNvSZhAT`)tMM*`x=h7{3U6t%#NLwJkM|e>+IlKaM zEjtpw5NtmVJzWA*T;C$*Y1qr!4fcT?%QKin#9vLjpJ}be;RnzkCH7u)0rVM!akNeG zP4Z$6>hlJhk>F6yLBbHfGUWp~8cs(+`+#fsdE#*UH;G+_|88RZg5+b^YtTJwnFL#G ze~nJg*Ln?#dJbF3fAtO@$w9$`#}#03EtnjS?itIm*yGVX^XQG@(%1Hvk_;Y&(7@hAt#05 zAC%0;Kep&&$aMUd;{SLS|0dc`DD2qDjl0BH+D?pEw&i4WKd+Jg@5$AMIpkm4i+Ow5 z8Z-xGN*Krva5`2SIZs=ie(yv?;hCdp`k`1nJHhy3*#EB(+n_F#nT0XXIH#Pmf8G6g z8%%8Tlmd^K*BETCCZb)uw7u*PnhG9EV81+?1y6;oaq)Z~{;$$!u69AjbcUg%zI{Zy zPbj_s(}RhW*Llq|_+JBO|J5+ZY<#KS#MX7j1k-o|GM~G8ZdYL^ zF1bJAb6;ThM&LXU3!w9fd6xBHs7a{Kk>oyS$>C2!Bi z*=K>w8>;laB*p>LXB7h({GBwv}V`RmE1tRlT?Un}H5A4}o8fZ54j{%4<#F8DoT9DdR|2+1PTn z5NILLLSQf>&{_Zob9NhRZgGjzSX Date: Tue, 9 Jun 2015 17:05:26 +0800 Subject: [PATCH 43/50] update license --- src/graphlab/engine/omni_engine.hpp | 26 +++++++++++++++++ .../engine/powerlyra_async_engine.hpp | 10 ++++--- src/graphlab/engine/powerlyra_sync_engine.hpp | 8 ++++-- src/graphlab/graph/builtin_parsers.hpp | 28 ++++++++++++++++++- src/graphlab/graph/distributed_graph.hpp | 26 +++++++++++++++++ ...distributed_bipartite_affinity_ingress.hpp | 25 +++++++++++++++++ .../distributed_bipartite_aweto_ingress.hpp | 25 +++++++++++++++++ .../distributed_bipartite_random_ingress.hpp | 27 +++++++++++++++++- .../distributed_hybrid_ginger_ingress.hpp | 27 +++++++++++++++++- .../ingress/distributed_hybrid_ingress.hpp | 27 +++++++++++++++++- 10 files changed, 219 insertions(+), 10 deletions(-) diff --git a/src/graphlab/engine/omni_engine.hpp b/src/graphlab/engine/omni_engine.hpp index a7ee946c01..ccc0ae5ac0 100644 --- a/src/graphlab/engine/omni_engine.hpp +++ b/src/graphlab/engine/omni_engine.hpp @@ -1,3 +1,29 @@ +/* + * Copyright (c) 2013 Shanghai Jiao Tong University. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * For more about this software visit: + * + * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html + * + * + * 2014.04 add calling to asynchronous engine of powerlyra + * 2013.11 add calling to synchronous engine of powerlyra + * + */ + /** * Copyright (c) 2009 Carnegie Mellon University. * All rights reserved. diff --git a/src/graphlab/engine/powerlyra_async_engine.hpp b/src/graphlab/engine/powerlyra_async_engine.hpp index abafc9995f..6ba80f0b48 100755 --- a/src/graphlab/engine/powerlyra_async_engine.hpp +++ b/src/graphlab/engine/powerlyra_async_engine.hpp @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2009 Carnegie Mellon University. +/* + * Copyright (c) 2013 Shanghai Jiao Tong University. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,13 +16,15 @@ * * For more about this software visit: * - * http://www.graphlab.ml.cmu.edu + * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html + * + * + * 2014.04 implement asynchronous engine of powerlyra * */ - #ifndef GRAPHLAB_POWERLYRA_ASYNC_ENGINE_HPP #define GRAPHLAB_POWERLYRA_ASYNC_ENGINE_HPP diff --git a/src/graphlab/engine/powerlyra_sync_engine.hpp b/src/graphlab/engine/powerlyra_sync_engine.hpp index d807b579d5..c3ee00a472 100644 --- a/src/graphlab/engine/powerlyra_sync_engine.hpp +++ b/src/graphlab/engine/powerlyra_sync_engine.hpp @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2013 Institute of Parallel and Distributed Systems, Shanghai Jiao Tong University. +/* + * Copyright (c) 2013 Shanghai Jiao Tong University. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,9 +18,13 @@ * * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html * + * + * 2013.11 implement synchronous engine of powerlyra + * */ + #ifndef GRAPHLAB_POWERLYRA_SYNC_ENGINE_HPP #define GRAPHLAB_POWERLYRA_SYNC_ENGINE_HPP diff --git a/src/graphlab/graph/builtin_parsers.hpp b/src/graphlab/graph/builtin_parsers.hpp index 2900a183ea..d395183c49 100644 --- a/src/graphlab/graph/builtin_parsers.hpp +++ b/src/graphlab/graph/builtin_parsers.hpp @@ -1,3 +1,29 @@ +/* + * Copyright (c) 2013 Shanghai Jiao Tong University. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * For more about this software visit: + * + * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html + * + * + * 2013.11 implement rtsv_parser for debugging + * + */ + + /** * Copyright (c) 2009 Carnegie Mellon University. * All rights reserved. @@ -96,7 +122,7 @@ namespace graphlab { } // end of tsv parser /** - * \brief Parse files in the reverse tsv format (for debug) + * \brief Parse files in the reverse tsv format (for debugging) * * This is identical to the tsv format but reverse edge direction. * diff --git a/src/graphlab/graph/distributed_graph.hpp b/src/graphlab/graph/distributed_graph.hpp index 9c483e0531..eadcfc4368 100644 --- a/src/graphlab/graph/distributed_graph.hpp +++ b/src/graphlab/graph/distributed_graph.hpp @@ -1,3 +1,29 @@ +/* + * Copyright (c) 2013 Shanghai Jiao Tong University. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * For more about this software visit: + * + * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html + * + * + * 2014.02 add calling to bipartitie-aware partitioning + * 2013.11 add calling to hybrid partitioning for power-law graphs + * + */ + /** * Copyright (c) 2009 Carnegie Mellon University. * All rights reserved. diff --git a/src/graphlab/graph/ingress/distributed_bipartite_affinity_ingress.hpp b/src/graphlab/graph/ingress/distributed_bipartite_affinity_ingress.hpp index fad6198970..b4a75861ee 100644 --- a/src/graphlab/graph/ingress/distributed_bipartite_affinity_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_bipartite_affinity_ingress.hpp @@ -1,3 +1,28 @@ +/* + * Copyright (c) 2013 Shanghai Jiao Tong University. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * For more about this software visit: + * + * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html + * + * + * 2014.04 implement bipartite-aware partitioning with affinity + * + */ + /** * Copyright (c) 2013 Institute of Parallel and Distributed Systems, Shanghai Jiao Tong University. * All rights reserved. diff --git a/src/graphlab/graph/ingress/distributed_bipartite_aweto_ingress.hpp b/src/graphlab/graph/ingress/distributed_bipartite_aweto_ingress.hpp index 417f473508..ec3b787c4c 100644 --- a/src/graphlab/graph/ingress/distributed_bipartite_aweto_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_bipartite_aweto_ingress.hpp @@ -1,3 +1,28 @@ +/* + * Copyright (c) 2013 Shanghai Jiao Tong University. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * For more about this software visit: + * + * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html + * + * + * 2014.04 implement bipartite-aware partitioning with heuristic (aweto) + * + */ + /** * Copyright (c) 2013 Institute of Parallel and Distributed Systems, Shanghai Jiao Tong University. * All rights reserved. diff --git a/src/graphlab/graph/ingress/distributed_bipartite_random_ingress.hpp b/src/graphlab/graph/ingress/distributed_bipartite_random_ingress.hpp index 5c0b8ee8ee..b9f6b7b55c 100644 --- a/src/graphlab/graph/ingress/distributed_bipartite_random_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_bipartite_random_ingress.hpp @@ -1,4 +1,29 @@ -/** +/* + * Copyright (c) 2013 Shanghai Jiao Tong University. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * For more about this software visit: + * + * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html + * + * + * 2014.04 implement bipartite-aware random partitioning + * + */ + + /** * Copyright (c) 2013 Institute of Parallel and Distributed Systems, Shanghai Jiao Tong University. * All rights reserved. * diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp index eef3e53504..8906448283 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp @@ -1,4 +1,29 @@ -/** +/* + * Copyright (c) 2013 Shanghai Jiao Tong University. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * For more about this software visit: + * + * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html + * + * + * 2013.11 implement hybrid partitioning with heuristic (ginger) + * + */ + + /** * Copyright (c) 2013 Institute of Parallel and Distributed Systems, Shanghai Jiao Tong University. * All rights reserved. * diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp index b3b693a2f4..9eb092a34d 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp @@ -1,4 +1,29 @@ -/** +/* + * Copyright (c) 2013 Shanghai Jiao Tong University. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * For more about this software visit: + * + * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html + * + * + * 2013.11 implement hybrid random partitioning + * + */ + + /** * Copyright (c) 2013 Institute of Parallel and Distributed Systems, Shanghai Jiao Tong University. * All rights reserved. * From 1e764d29299141fba2109e6126ddf443c5e248a1 Mon Sep 17 00:00:00 2001 From: Chen Rong Date: Tue, 9 Jun 2015 17:08:29 +0800 Subject: [PATCH 44/50] update license --- ...distributed_bipartite_affinity_ingress.hpp | 22 ------------------ .../distributed_bipartite_aweto_ingress.hpp | 22 ------------------ .../distributed_bipartite_random_ingress.hpp | 21 ----------------- .../distributed_hybrid_ginger_ingress.hpp | 22 ------------------ .../ingress/distributed_hybrid_ingress.hpp | 23 ------------------- 5 files changed, 110 deletions(-) diff --git a/src/graphlab/graph/ingress/distributed_bipartite_affinity_ingress.hpp b/src/graphlab/graph/ingress/distributed_bipartite_affinity_ingress.hpp index b4a75861ee..c17ddc7f55 100644 --- a/src/graphlab/graph/ingress/distributed_bipartite_affinity_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_bipartite_affinity_ingress.hpp @@ -23,28 +23,6 @@ * */ -/** - * Copyright (c) 2013 Institute of Parallel and Distributed Systems, Shanghai Jiao Tong University. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an "AS - * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language - * governing permissions and limitations under the License. - * - * For more about this software visit: - * - * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html - * - */ - #ifndef GRAPHLAB_DISTRIBUTED_BIPARTITE_AFFINITY_INGRESS_HPP #define GRAPHLAB_DISTRIBUTED_BIPARTITE_AFFINITY_INGRESS_HPP diff --git a/src/graphlab/graph/ingress/distributed_bipartite_aweto_ingress.hpp b/src/graphlab/graph/ingress/distributed_bipartite_aweto_ingress.hpp index ec3b787c4c..17a02e2975 100644 --- a/src/graphlab/graph/ingress/distributed_bipartite_aweto_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_bipartite_aweto_ingress.hpp @@ -23,28 +23,6 @@ * */ -/** - * Copyright (c) 2013 Institute of Parallel and Distributed Systems, Shanghai Jiao Tong University. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an "AS - * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language - * governing permissions and limitations under the License. - * - * For more about this software visit: - * - * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html - * - */ - #ifndef GRAPHLAB_DISTRIBUTED_BIPARTITE_AWETO_INGRESS_HPP #define GRAPHLAB_DISTRIBUTED_BIPARTITE_AWETO_INGRESS_HPP diff --git a/src/graphlab/graph/ingress/distributed_bipartite_random_ingress.hpp b/src/graphlab/graph/ingress/distributed_bipartite_random_ingress.hpp index b9f6b7b55c..e42e2d0c4c 100644 --- a/src/graphlab/graph/ingress/distributed_bipartite_random_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_bipartite_random_ingress.hpp @@ -23,27 +23,6 @@ * */ - /** - * Copyright (c) 2013 Institute of Parallel and Distributed Systems, Shanghai Jiao Tong University. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an "AS - * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language - * governing permissions and limitations under the License. - * - * For more about this software visit: - * - * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html - * - */ #ifndef GRAPHLAB_DISTRIBUTED_BIPARTITE_RANDOM_INGRESS_HPP #define GRAPHLAB_DISTRIBUTED_BIPARTITE_RANDOM_INGRESS_HPP diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp index 8906448283..9af3fc5c2c 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ginger_ingress.hpp @@ -23,28 +23,6 @@ * */ - /** - * Copyright (c) 2013 Institute of Parallel and Distributed Systems, Shanghai Jiao Tong University. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an "AS - * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language - * governing permissions and limitations under the License. - * - * For more about this software visit: - * - * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html - * - */ - #ifndef GRAPHLAB_DISTRIBUTED_HYBRID_GINGER_INGRESS_HPP #define GRAPHLAB_DISTRIBUTED_HYBRID_GINGER_INGRESS_HPP diff --git a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp index 9eb092a34d..520c90fd9e 100644 --- a/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp +++ b/src/graphlab/graph/ingress/distributed_hybrid_ingress.hpp @@ -23,29 +23,6 @@ * */ - /** - * Copyright (c) 2013 Institute of Parallel and Distributed Systems, Shanghai Jiao Tong University. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an "AS - * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language - * governing permissions and limitations under the License. - * - * For more about this software visit: - * - * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html - * - */ - - #ifndef GRAPHLAB_DISTRIBUTED_HYBRID_INGRESS_HPP #define GRAPHLAB_DISTRIBUTED_HYBRID_INGRESS_HPP From 60f2d696a908886452bb839012287684e93ed9ed Mon Sep 17 00:00:00 2001 From: Chen Rong Date: Tue, 9 Jun 2015 17:12:26 +0800 Subject: [PATCH 45/50] update license --- apps/example/word_search.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/example/word_search.cpp b/apps/example/word_search.cpp index aed18bc923..beda2d7380 100644 --- a/apps/example/word_search.cpp +++ b/apps/example/word_search.cpp @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2013 Institute of Parallel and Distributed Systems, Shanghai Jiao Tong University. +/* + * Copyright (c) 2013 Shanghai Jiao Tong University. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,9 +18,14 @@ * * http://ipads.se.sjtu.edu.cn/projects/powerlyra.html * + * + * 2014.02 implement word search application for testing bipartite-aware partitiong + * with affinity + * */ + #include #include #include From 6c20aa09635ef02a143ae4541c575f5db9b28ca4 Mon Sep 17 00:00:00 2001 From: Chen Rong Date: Sat, 13 Jun 2015 17:26:30 +0800 Subject: [PATCH 46/50] add plasync support for graph coloring --- toolkits/graph_analytics/simple_coloring.cpp | 47 +++++++++++++------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/toolkits/graph_analytics/simple_coloring.cpp b/toolkits/graph_analytics/simple_coloring.cpp index 70efb3cd59..fc60c5c728 100644 --- a/toolkits/graph_analytics/simple_coloring.cpp +++ b/toolkits/graph_analytics/simple_coloring.cpp @@ -178,31 +178,32 @@ size_t validate_conflict(graph_type::edge_type& edge) { int main(int argc, char** argv) { - - //global_logger().set_log_level(LOG_INFO); - // Initialize control plane using mpi graphlab::mpi_tools::init(argc, argv); graphlab::distributed_control dc; - + global_logger().set_log_level(LOG_INFO); dc.cout() << "This program computes a simple graph coloring of a" "provided graph.\n\n"; + // Parse command line options ----------------------------------------------- graphlab::command_line_options clopts("Graph coloring. " "Given a graph, this program computes a graph coloring of the graph." "The Asynchronous engine is used."); std::string prefix, format; std::string output; float alpha = 2.1; - size_t powerlaw = 0; + std::string exec_type = "asynchronous"; clopts.attach_option("graph", prefix, "Graph input. reads all graphs matching prefix*"); + clopts.attach_option("engine", exec_type, + "The asynchronous engine type (async or plasync)"); clopts.attach_option("format", format, "The graph format"); - clopts.attach_option("output", output, + clopts.attach_option("output", output, "A prefix to save the output."); - clopts.attach_option("powerlaw", powerlaw, + size_t powerlaw = 0; + clopts.attach_option("powerlaw", powerlaw, "Generate a synthetic powerlaw out-degree graph. "); clopts.attach_option("alpha", alpha, "Alpha in powerlaw distrubution"); @@ -219,10 +220,17 @@ int main(int argc, char** argv) { } - graphlab::launch_metric_server(); - // load graph - graph_type graph(dc, clopts); + if (exec_type != "asynchronous" && exec_type != "async" + && exec_type != "powerlyra_asynchronous" && exec_type != "plasync"){ + dc.cout() << "Only supports asynchronous engine" << std::endl; + clopts.print_description(); + return EXIT_FAILURE; + } + graphlab::launch_metric_server(); + // Build the graph ---------------------------------------------------------- + graphlab::timer ti; + graph_type graph(dc, clopts); if(powerlaw > 0) { // make a synthetic graph dc.cout() << "Loading synthetic Powerlaw graph." << std::endl; graph.load_synthetic_powerlaw(powerlaw, false, alpha, 100000000); @@ -237,12 +245,15 @@ int main(int argc, char** argv) { } graph.load_format(prefix, format); } + + ti.start(); graph.finalize(); + dc.cout() << "Finalizing graph. Finished in " + << ti.current_time() << " (seconds)" << std::endl; dc.cout() << "Number of vertices: " << graph.num_vertices() << std::endl << "Number of edges: " << graph.num_edges() << std::endl; - graphlab::timer ti; // create engine to count the number of triangles dc.cout() << "Coloring..." << std::endl; @@ -251,16 +262,22 @@ int main(int argc, char** argv) { } else { clopts.get_engine_args().set_option("factorized", true); } - graphlab::async_consistent_engine engine(dc, graph, clopts); + + + // Running The Engine ------------------------------------------------------- + graphlab::omni_engine engine(dc, graph, exec_type, clopts); engine.signal_all(); + ti.start(); engine.start(); - - dc.cout() << "Colored in " << ti.current_time() << " seconds" << std::endl; + dc.cout() << "------------------------------------------------" << std::endl; + dc.cout() << "Colored in " << ti.current_time() << " (seconds)" << std::endl; dc.cout() << "Colored using " << used_colors.size() << " colors" << std::endl; - + size_t conflict_count = graph.map_reduce_edges(validate_conflict); dc.cout() << "Num conflicts = " << conflict_count << "\n"; + + // Save the final graph ----------------------------------------------------- if (output != "") { graph.save(output, save_colors(), From c2b4899e1f3c2d47ee4f2ec42bcaec0a4c01ed41 Mon Sep 17 00:00:00 2001 From: Chen Rong Date: Sun, 14 Jun 2015 13:06:44 +0800 Subject: [PATCH 47/50] refine output of coloring --- toolkits/graph_analytics/simple_coloring.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/toolkits/graph_analytics/simple_coloring.cpp b/toolkits/graph_analytics/simple_coloring.cpp index fc60c5c728..9b74479b9b 100644 --- a/toolkits/graph_analytics/simple_coloring.cpp +++ b/toolkits/graph_analytics/simple_coloring.cpp @@ -270,10 +270,17 @@ int main(int argc, char** argv) { ti.start(); engine.start(); - dc.cout() << "------------------------------------------------" << std::endl; - dc.cout() << "Colored in " << ti.current_time() << " (seconds)" << std::endl; + const double runtime = ti.current_time(); + dc.cout() << "----------------------------------------------------------" + << std::endl + << "Final Runtime (seconds): " << runtime + << std::endl + << "Updates executed: " << engine.num_updates() << std::endl + << "Update Rate (updates/second): " + << engine.num_updates() / runtime << std::endl; dc.cout() << "Colored using " << used_colors.size() << " colors" << std::endl; + size_t conflict_count = graph.map_reduce_edges(validate_conflict); dc.cout() << "Num conflicts = " << conflict_count << "\n"; From d3a470989a8c27b13f98208e42b3de019d3870d4 Mon Sep 17 00:00:00 2001 From: Chen Rong Date: Tue, 16 Jun 2015 12:33:38 +0800 Subject: [PATCH 48/50] refine time estimation --- toolkits/collaborative_filtering/als.cpp | 21 ++++++---- toolkits/collaborative_filtering/svd.cpp | 36 +++++++++++----- .../graph_analytics/approximate_diameter.cpp | 38 +++++++++++++---- .../graph_analytics/connected_component.cpp | 41 +++++++++++++++---- toolkits/graph_analytics/pagerank.cpp | 12 +++++- toolkits/graph_analytics/simple_coloring.cpp | 30 ++++++++++---- toolkits/graph_analytics/sssp.cpp | 13 +++++- 7 files changed, 146 insertions(+), 45 deletions(-) diff --git a/toolkits/collaborative_filtering/als.cpp b/toolkits/collaborative_filtering/als.cpp index d434df045d..5221531df3 100644 --- a/toolkits/collaborative_filtering/als.cpp +++ b/toolkits/collaborative_filtering/als.cpp @@ -619,8 +619,9 @@ int main(int argc, char** argv) { graphlab::timer timer; graph_type graph(dc, clopts); graph.load(input_dir, graph_loader); + const double loading = timer.current_time(); dc.cout() << "Loading graph. Finished in " - << timer.current_time() << std::endl; + << loading << std::endl; if (dc.procid() == 0) add_implicit_edges(implicitratingtype, graph, dc); @@ -628,11 +629,19 @@ int main(int argc, char** argv) { dc.cout() << "Finalizing graph." << std::endl; timer.start(); graph.finalize(); + const double finalizing = timer.current_time(); dc.cout() << "Finalizing graph. Finished in " - << timer.current_time() << std::endl; + << finalizing << std::endl; + + // NOTE: ingress time = loading time + finalizing time + const double ingress = loading + finalizing; + dc.cout() << "Final Ingress (second): " << ingress << std::endl; if (!graph.num_edges() || !graph.num_vertices()) - logstream(LOG_FATAL)<< "Failed to load graph. Check your input path: " << input_dir << std::endl; + logstream(LOG_FATAL) << "Failed to load graph. Check your input path: " + << input_dir << std::endl; + dc.cout() << "#vertices: " << graph.num_vertices() + << " #edges:" << graph.num_edges() << std::endl; dc.cout() @@ -699,13 +708,11 @@ int main(int argc, char** argv) { true, threads_per_machine); //save the linear model graph.save(predictions + ".U", linear_model_saver_U(), - gzip_output, true, false, threads_per_machine); + gzip_output, true, false, threads_per_machine); graph.save(predictions + ".V", linear_model_saver_V(), - gzip_output, true, false, threads_per_machine); + gzip_output, true, false, threads_per_machine); } - - graphlab::mpi_tools::finalize(); return EXIT_SUCCESS; diff --git a/toolkits/collaborative_filtering/svd.cpp b/toolkits/collaborative_filtering/svd.cpp index 26a99235f6..19aec7ede0 100644 --- a/toolkits/collaborative_filtering/svd.cpp +++ b/toolkits/collaborative_filtering/svd.cpp @@ -702,16 +702,28 @@ int main(int argc, char** argv) { graph_type graph(dc, clopts); graph.load(input_dir, graph_loader); pgraph = &graph; + const double loading = timer.current_time(); dc.cout() << "Loading graph. Finished in " - << timer.current_time() << std::endl; + << loading << std::endl; + + dc.cout() << "Finalizing graph." << std::endl; timer.start(); graph.finalize(); + const double finalizing = timer.current_time(); dc.cout() << "Finalizing graph. Finished in " - << timer.current_time() << std::endl; + << finalizing << std::endl; + + // NOTE: ingress time = loading time + finalizing time + const double ingress = loading + finalizing; + dc.cout() << "Final Ingress (second): " << ingress << std::endl; + if (!graph.num_edges() || !graph.num_vertices()) - logstream(LOG_FATAL)<< "Failed to load graph. Check your input path: " << input_dir << std::endl; + logstream(LOG_FATAL) << "Failed to load graph. Check your input path: " + << input_dir << std::endl; + dc.cout() << "#vertices: " << graph.num_vertices() + << " #edges:" << graph.num_edges() << std::endl; dc.cout() << "========== Graph statistics on proc " << dc.procid() @@ -753,7 +765,8 @@ int main(int argc, char** argv) { for (int i=0; i< rows; i++){ int rc = fscanf(file, "%lg\n", &val); if (rc != 1) - logstream(LOG_FATAL)<<"Failed to read initial vector (on line: "<< i << " ) " << std::endl; + logstream(LOG_FATAL)<<"Failed to read initial vector (on line: "<< i + << " ) " << std::endl; input[i] = val; } fclose(file); @@ -765,16 +778,17 @@ int main(int argc, char** argv) { lanczos( info, timer, errest, vecfile); if (graphlab::mpi_tools::rank()==0) - write_output_vector(predictions + ".singular_values", singular_values, false, "%GraphLab SVD Solver library. This file contains the singular values."); + write_output_vector(predictions + ".singular_values", singular_values, false, + "%GraphLab SVD Solver library. This file contains the singular values."); const double runtime = timer.current_time(); dc.cout() << "----------------------------------------------------------" - << std::endl - << "Final Runtime (seconds): " << runtime - << std::endl - << "Updates executed: " << engine.num_updates() << std::endl - << "Update Rate (updates/second): " - << engine.num_updates() / runtime << std::endl; + << std::endl + << "Final Runtime (seconds): " << runtime + << std::endl + << "Updates executed: " << engine.num_updates() << std::endl + << "Update Rate (updates/second): " + << engine.num_updates() / runtime << std::endl; // Compute the final training error ----------------------------------------- if (unittest == 1){ diff --git a/toolkits/graph_analytics/approximate_diameter.cpp b/toolkits/graph_analytics/approximate_diameter.cpp index e15c8b8282..c5ec778488 100644 --- a/toolkits/graph_analytics/approximate_diameter.cpp +++ b/toolkits/graph_analytics/approximate_diameter.cpp @@ -307,14 +307,31 @@ int main(int argc, char** argv) { } //load graph - graph_type graph(dc, clopts); dc.cout() << "Loading graph in format: "<< format << std::endl; + graphlab::timer timer; + graph_type graph(dc, clopts); graph.load_format(graph_dir, format); + const double loading = timer.current_time(); + dc.cout() << "Loading graph. Finished in " + << loading << std::endl; + + // must call finalize before querying the graph + dc.cout() << "Finalizing graph." << std::endl; + timer.start(); graph.finalize(); + const double finalizing = timer.current_time(); + dc.cout() << "Finalizing graph. Finished in " + << finalizing << std::endl; + + // NOTE: ingress time = loading time + finalizing time + const double ingress = loading + finalizing; + dc.cout() << "Final Ingress (second): " << ingress << std::endl; + + + dc.cout() << "#vertices: " << graph.num_vertices() + << " #edges:" << graph.num_edges() << std::endl; - time_t start, end; //initialize vertices - time(&start); if (use_sketch == false) graph.transform_vertices(initialize_vertex); else @@ -322,6 +339,7 @@ int main(int argc, char** argv) { graphlab::omni_engine engine(dc, graph, exec_type, clopts); + timer.start(); //main iteration size_t previous_count = 0; size_t diameter = 0; @@ -348,13 +366,19 @@ int main(int argc, char** argv) { } previous_count = current_count; } - time(&end); - dc.cout() << "graph calculation time is " << (end - start) << " sec\n"; - dc.cout() << "The approximate diameter is " << diameter << "\n"; + const double runtime = timer.current_time(); + dc.cout() << "----------------------------------------------------------" + << std::endl + << "Final Runtime (seconds): " << runtime + << std::endl + << "Updates executed: " << engine.num_updates() << std::endl + << "Update Rate (updates/second): " + << engine.num_updates() / runtime << std::endl; - graphlab::mpi_tools::finalize(); + dc.cout() << "The approximate diameter is " << diameter << std::endl; + graphlab::mpi_tools::finalize(); return EXIT_SUCCESS; } diff --git a/toolkits/graph_analytics/connected_component.cpp b/toolkits/graph_analytics/connected_component.cpp index 6f7739c8fc..7bdcd14e68 100644 --- a/toolkits/graph_analytics/connected_component.cpp +++ b/toolkits/graph_analytics/connected_component.cpp @@ -175,24 +175,51 @@ int main(int argc, char** argv) { std::cout << "--graph is not optional\n"; return EXIT_FAILURE; } - - graph_type graph(dc, clopts); - + //load graph dc.cout() << "Loading graph in format: "<< format << std::endl; + graphlab::timer timer; + graph_type graph(dc, clopts); graph.load_format(graph_dir, format); - graphlab::timer ti; + const double loading = timer.current_time(); + dc.cout() << "Loading graph. Finished in " + << loading << std::endl; + + + dc.cout() << "Finalizing graph." << std::endl; + timer.start(); graph.finalize(); - dc.cout() << "Finalization in " << ti.current_time() << std::endl; + const double finalizing = timer.current_time(); + dc.cout() << "Finalizing graph. Finished in " + << finalizing << std::endl; + + // NOTE: ingress time = loading time + finalizing time + const double ingress = loading + finalizing; + dc.cout() << "Final Ingress (second): " << ingress << std::endl; + + + dc.cout() << "#vertices: " << graph.num_vertices() + << " #edges:" << graph.num_edges() << std::endl; + + // init graph.transform_vertices(initialize_vertex); //running the engine - time_t start, end; graphlab::omni_engine engine(dc, graph, exec_type, clopts); engine.signal_all(); - time(&start); + timer.start(); engine.start(); + const double runtime = timer.current_time(); + dc.cout() << "----------------------------------------------------------" + << std::endl + << "Final Runtime (seconds): " << runtime + << std::endl + << "Updates executed: " << engine.num_updates() << std::endl + << "Update Rate (updates/second): " + << engine.num_updates() / runtime << std::endl; + + //write results if (saveprefix.size() > 0) { graph.save(saveprefix, graph_writer(), diff --git a/toolkits/graph_analytics/pagerank.cpp b/toolkits/graph_analytics/pagerank.cpp index 4a63c14128..b11e603a94 100644 --- a/toolkits/graph_analytics/pagerank.cpp +++ b/toolkits/graph_analytics/pagerank.cpp @@ -239,14 +239,22 @@ int main(int argc, char** argv) { clopts.print_description(); return 0; } + const double loading = timer.current_time(); dc.cout() << "Loading graph. Finished in " - << timer.current_time() << std::endl; + << loading << std::endl; + + // must call finalize before querying the graph dc.cout() << "Finalizing graph." << std::endl; timer.start(); graph.finalize(); + const double finalizing = timer.current_time(); dc.cout() << "Finalizing graph. Finished in " - << timer.current_time() << std::endl; + << finalizing << std::endl; + + // NOTE: ingress time = loading time + finalizing time + const double ingress = loading + finalizing; + dc.cout() << "Final Ingress (second): " << ingress << std::endl; dc.cout() << "#vertices: " << graph.num_vertices() << " #edges:" << graph.num_edges() << std::endl; diff --git a/toolkits/graph_analytics/simple_coloring.cpp b/toolkits/graph_analytics/simple_coloring.cpp index 9b74479b9b..3dde327a1d 100644 --- a/toolkits/graph_analytics/simple_coloring.cpp +++ b/toolkits/graph_analytics/simple_coloring.cpp @@ -227,9 +227,11 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } - graphlab::launch_metric_server(); + graphlab::launch_metric_server(); + // Build the graph ---------------------------------------------------------- - graphlab::timer ti; + dc.cout() << "Loading graph." << std::endl; + graphlab::timer timer; graph_type graph(dc, clopts); if(powerlaw > 0) { // make a synthetic graph dc.cout() << "Loading synthetic Powerlaw graph." << std::endl; @@ -245,14 +247,24 @@ int main(int argc, char** argv) { } graph.load_format(prefix, format); } - - ti.start(); + const double loading = timer.current_time(); + dc.cout() << "Loading graph. Finished in " + << loading << std::endl; + + // must call finalize before querying the graph + dc.cout() << "Finalizing graph." << std::endl; + timer.start(); graph.finalize(); + const double finalizing = timer.current_time(); dc.cout() << "Finalizing graph. Finished in " - << ti.current_time() << " (seconds)" << std::endl; + << finalizing << std::endl; + + // NOTE: ingress time = loading time + finalizing time + const double ingress = loading + finalizing; + dc.cout() << "Final Ingress (second): " << ingress << std::endl; - dc.cout() << "Number of vertices: " << graph.num_vertices() << std::endl - << "Number of edges: " << graph.num_edges() << std::endl; + dc.cout() << "#vertices: " << graph.num_vertices() + << " #edges:" << graph.num_edges() << std::endl; // create engine to count the number of triangles @@ -267,10 +279,10 @@ int main(int argc, char** argv) { // Running The Engine ------------------------------------------------------- graphlab::omni_engine engine(dc, graph, exec_type, clopts); engine.signal_all(); - ti.start(); + timer.start(); engine.start(); - const double runtime = ti.current_time(); + const double runtime = timer.current_time(); dc.cout() << "----------------------------------------------------------" << std::endl << "Final Runtime (seconds): " << runtime diff --git a/toolkits/graph_analytics/sssp.cpp b/toolkits/graph_analytics/sssp.cpp index 8aa0454c05..bc8df15e4c 100644 --- a/toolkits/graph_analytics/sssp.cpp +++ b/toolkits/graph_analytics/sssp.cpp @@ -281,14 +281,23 @@ int main(int argc, char** argv) { clopts.print_description(); return EXIT_FAILURE; } + const double loading = timer.current_time(); dc.cout() << "Loading graph. Finished in " - << timer.current_time() << std::endl; + << loading << std::endl; + // must call finalize before querying the graph dc.cout() << "Finalizing graph." << std::endl; timer.start(); graph.finalize(); + const double finalizing = timer.current_time(); dc.cout() << "Finalizing graph. Finished in " - << timer.current_time() << std::endl; + << finalizing << std::endl; + + // NOTE: ingress time = loading time + finalizing time + const double ingress = loading + finalizing; + dc.cout() << "Final Ingress (second): " << ingress << std::endl; + + dc.cout() << "Final Ingress (second): " << ingress << std::endl; dc.cout() << "#vertices: " << graph.num_vertices() << " #edges:" << graph.num_edges() << std::endl; From 44e9373d3f89a26b2247dcdbb5ac47756d3d9c88 Mon Sep 17 00:00:00 2001 From: Chen Rong Date: Tue, 23 Jun 2015 12:39:23 +0800 Subject: [PATCH 49/50] simulate theory analysis in paper --- tests/simulate_powerlaw_replica.cpp | 89 +++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 tests/simulate_powerlaw_replica.cpp diff --git a/tests/simulate_powerlaw_replica.cpp b/tests/simulate_powerlaw_replica.cpp new file mode 100644 index 0000000000..c061c3a1a3 --- /dev/null +++ b/tests/simulate_powerlaw_replica.cpp @@ -0,0 +1,89 @@ +#include +#include +using namespace std; + +double h(int num,double alpha){ + double sum=0; + for(int i=1;i<=num;i++){ + sum+=pow(i,-alpha); + } + return sum; +} +double random_replication(int V,double alpha,int p){ + double E=h(V,alpha-1)/h(V,alpha)*V; + double tmp=V/h(V,alpha); + double sum=0; + for(int i=1;i<=V;i++){ + double num_replica=p*(1-pow((1-1.0/p),i+E/V)); + num_replica+=(p-num_replica)/p; + sum+=tmp * pow(i,-alpha)* num_replica; + } + return sum/V; +} + +double grid_replication(int V,double alpha,int p){ + double E=h(V,alpha-1)/h(V,alpha)*V; + double fp=2*pow(p,0.5)-1; + double tmp=V/h(V,alpha); + double sum=0; + for(int i=1;i<=V;i++){ + double num_replica=fp*(1-pow((1-1.0/fp),i+E/V)); + num_replica+=(fp-num_replica)/fp; + sum+=tmp * pow(i,-alpha)* num_replica; + } + return sum/V; +} +double hybrid_replication(int V,double alpha,int p,int threshold){ + double E=h(V,alpha-1)/h(V,alpha)*V; + double tmp=V/h(V,alpha); + double sum=0; + double R_E_H=1-h(threshold,alpha-1)/h(V,alpha-1); + for(int i=1;i<=V;i++){ + double num_replica; + if(i<=threshold){ + num_replica=p*(1-pow((1-1.0/p),E/V*(1-R_E_H))); + } else { + num_replica=p*(1-pow((1-1.0/p),i+E/V*(1-R_E_H))); + } + num_replica+=(p-num_replica)/p; + sum+=tmp * pow(i,-alpha)* num_replica; + } + return sum/V; +} + +int main(){ + int V=10000000;//10m + cout<<"alpha\trandom\tgrid\thybrid\tp=48"< Date: Wed, 30 Mar 2022 14:07:33 +0800 Subject: [PATCH 50/50] Update TUTORIALS.md There are many typos in the TUTORIAL file, which makes it hard to read. --- TUTORIALS.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TUTORIALS.md b/TUTORIALS.md index 9e1077093e..ad476d93a6 100644 --- a/TUTORIALS.md +++ b/TUTORIALS.md @@ -1,6 +1,6 @@ # GraphLab PowerGraph Tutorials -##Table of Contents +## Table of Contents * [Deploying on AWS EC2 Cluster](#ec2) * [Deploying in a Cluster](#cluster) * [Deploying on a single multicore machine](#multicore) @@ -142,7 +142,7 @@ wget http://www.select.cs.cmu.edu/code/graphlab/datasets/smallnetflix_mm.validat ``` Now run GraphLab: -```` +``` mpiexec -n 2 -hostfile ~/machines /path/to/als --matrix /some/ns/folder/smallnetflix/ --max_iter=3 --ncpus=1 --minval=1 --maxval=5 --predictions=out_file ``` Where -n is the number of MPI nodes, and –ncpus is the number of deployed cores on each MPI node. @@ -240,7 +240,7 @@ or: Check that all machines have access to, or are using the same binary -#Deployment on a single multicore machine +# Deployment on a single multicore machine ## Preliminaries: @@ -352,7 +352,7 @@ Here is a more detailed explanation of the benchmarking process. The benchmarkin 5. In case you would like to benchmark a different algorithm, you can add an additional youralgo_demo section into the gl_ec2.py script. 6. In case you would like to bechmark a regular instance, simply change the following line in gl_ec2.py from -```` +``` ./gl-ec2 -i ~/.ssh/amazonec2.pem -k amazonec2 -a hpc -s $MAX_SLAVES -t cc2.8xlarge launch hpctest ``` to: @@ -436,7 +436,7 @@ Previous to the program execution, the graph is first loaded into memory and par or -```` +``` --graph_opts="ingress=grid" # works for power of 2 sized cluster i.e. 2,4,8,.. machines ```