使Clojure交易速度加快

时间:2018-05-09 12:25:14

标签: concurrency clojure parallel-processing

我在Clojure有两个客户航班预订实施。第一个是顺序的,第二个是我尝试并行化。我的理解是并行实现应该更快。 顺序实现使用原子,而并行实现使用refs。输入只是两个集合 - 一组航班和一组客户。预订流程涉及根据客户预算和目的地调整航班。还有一个调整航班价格的销售流程。请注意,销售流程以及客户和航班集合对于两种实施都是相同的。

以下是顺序实施。

(ns flight-reservation
  (:require [clojure.string]
            [clojure.pprint]
            #_[input-simple :as input]
            [input-random :as input]))

(def logger (agent nil))
(defn log [& msgs] (send logger (fn [_] (apply println msgs))))
;(defn log [& msgs] nil)

(def flights
  "All flights are encapsulated in a single atom in this implementation.
  You are free to change this to a more appropriate mechanism."
  (atom []))

(defn initialize-flights [initial-flights]
  "Set `flights` atom to the `initial-flights`."
  (reset! flights initial-flights))

(defn print-flights [flights]
  "Print `flights`."
  (letfn [(pricing->str [pricing]
            (->> pricing
              (map (fn [[p a t]] (clojure.pprint/cl-format nil "$~3d: ~3d ~3d" p a t)))
              (clojure.string/join ", ")))]
    (doseq [{:keys [id from to pricing]} flights]
      (println (clojure.pprint/cl-format nil "Flight ~3d from ~a to ~a: ~a"
        id from to (pricing->str pricing))))))

(defn- update-pricing [flight factor]
  "Updated pricing of `flight` with `factor`."
  (update flight :pricing
    #(map (fn [[p a t]] [(* p factor) a t]) %)))

(defn start-sale [flight-ids]
  "Sale: -20% on `flight-ids`."
  (log "Start sale for flights" flight-ids)
  (swap! flights
    (fn [old-flights]
      (vec (map
             (fn [flight]
               (if (contains? flight-ids (:id flight))
                 (update-pricing flight 0.80)
                 flight))
             old-flights)))))

(defn end-sale [flight-ids]
  "End sale: +25% (inverse of -20%) on `flight-ids`."
  (log "End sale")
  (swap! flights
    (fn [old-flights]
      (vec (map
             (fn [flight]
               (if (contains? flight-ids (:id flight))
                 (update-pricing flight 1.25)
                 flight))
             old-flights)))))

(defn sort-pricing [pricing]
  "Sort `pricing` from lowest to highest price."
  (sort-by first pricing))

(defn filter-pricing-with-n-seats [pricing seats]
  "Get `pricing` for which there are at least `seats` empty seats available."
  (filter #(>= (second %) seats) pricing))

(defn lowest-available-price [flight seats]
  "Returns the lowest price in `flight` for which at least `seats` empty seats
  are available, or nil if none found."
  (-> (:pricing flight)                 ; [[price available taken]]
    (filter-pricing-with-n-seats seats)
    (sort-pricing)
    (first)                             ; [price available taken]
    (first)))                           ; price

(defn- find-flight [flights customer]
  "Find a flight in `flights` that is on the route and within the budget of
  `customer`. If a flight was found, returns {:flight flight :price price},
  else returns nil."
  (let [{:keys [_id from to seats budget]}
          customer
        flights-and-prices
          ; flights that are on the route and within budget, and their price
          (for [f flights
                :when (and (= (:from f) from) (= (:to f) to))
                :let [lowest-price (lowest-available-price f seats)]
                :when (and (some? lowest-price) (<= lowest-price budget))]
            {:flight f :price lowest-price})
        cheapest-flight-and-price
          (first (sort-by :price flights-and-prices))]
    cheapest-flight-and-price))

(defn- book [flight price seats]
  "Updates `flight` to book `seats` at `price`."
  (update flight :pricing
    (fn [pricing]
      (for [[p a t] pricing]
        (if (= p price)
          [p (- a seats) (+ t seats)]
          [p a t])))))

(defn- process-customer [flights customer]
  "Try to book a flight from `flights` for `customer`, returning the updated
  flight if found, or nil if no suitable flight was found."
  (if-let [{:keys [flight price]} (find-flight flights customer)]
    (let [updated-flight (book flight price (:seats customer))]
      (log "Customer" (:id customer) "booked" (:seats customer)
        "seats on flight" (:id updated-flight) "at $" price " (< budget of $"
        (:budget customer) ").")
      updated-flight)
    (do
      (log "Customer" (:id customer) "did not find a flight.")
      nil)))

(def finished-processing?
  "Set to true once all customers have been processed, so that sales process
  can end."
  (atom false))

(defn process-customers [customers]
  (Thread/sleep 100)
  "Process `customers` one by one."
  (doseq [customer customers]
    (swap! flights
      (fn [flights]
        (if-let [updated-flight (process-customer flights customer)]
          (assoc flights (:id updated-flight) updated-flight)
          flights))))
  (reset! finished-processing? true))

(defn sales-process []
  "The sales process starts and ends sales periods, until `finished-processing?`
  is true."
  (loop []
    (let [discounted-flight-ids (->> input/flights
                                     (map :id)
                                     shuffle
                                     (take input/NUMBER_OF_DISCOUNTED_FLIGHTS)
                                     set)]
      (Thread/sleep input/TIME_BETWEEN_SALES)
      (start-sale discounted-flight-ids)
      (Thread/sleep input/TIME_OF_SALES)
      (end-sale discounted-flight-ids))
    (if (not @finished-processing?)
      (recur))))

(defn main []
  (initialize-flights input/flights)
  (let [f1 (future (time (process-customers input/customers)))
        f2 (future (sales-process))]
    @f1
    @f2)
  (println "Flights:")
  (print-flights @flights))

(main)
(shutdown-agents)

以下是并行实现:

(ns flight-reservation
  (:require [clojure.string]
            [clojure.pprint]
            #_[input-simple :as input]
            [input-random :as input]))
(def N-THREADS 32) 

(def logger (agent nil))
(defn log [& msgs] (send logger (fn [_] (apply println msgs))))
;(defn log [& msgs] nil)

(def flights
  "All flights are encapsulated in a single atom in this implementation.
  You are free to change this to a more appropriate mechanism."
  (ref []))

(defn initialize-flights [initial-flights]
  "Set `flights` atom to the `initial-flights`."
  (dosync(ref-set flights initial-flights)))

(defn print-flights [flights]
  "Print `flights`."
  (letfn [(pricing->str [pricing]
            (->> pricing
              (map (fn [[p a t]] (clojure.pprint/cl-format nil "$~3d: ~3d ~3d" p a t)))
              (clojure.string/join ", ")))]
    (doseq [{:keys [id from to pricing]} flights]
      (println (clojure.pprint/cl-format nil "Flight ~3d from ~a to ~a: ~a"
        id from to (pricing->str pricing))))))

(defn- update-pricing [flight factor]
  "Updated pricing of `flight` with `factor`."
  (update flight :pricing
    #(map (fn [[p a t]] [(* p factor) a t]) %)))

(defn start-sale [flight-ids]
  "Sale: -20% on `flight-ids`."
  (log "Start sale for flights" flight-ids)
  (dosync
  (alter flights
    (fn [old-flights]
      (vec (pmap
             (fn [flight]
               (if (contains? flight-ids (:id flight))
                 (update-pricing flight 0.80)
                 flight))
             old-flights))))))

(defn end-sale [flight-ids]
  "End sale: +25% (inverse of -20%) on `flight-ids`."
  (log "End sale")
  (dosync
  (alter flights
    (fn [old-flights]
      (vec (pmap
             (fn [flight]
               (if (contains? flight-ids (:id flight))
                 (update-pricing flight 1.25)
                 flight))
             old-flights))))))

(defn sort-pricing [pricing]
  "Sort `pricing` from lowest to highest price."
  (sort-by first pricing))

(defn filter-pricing-with-n-seats [pricing seats]
  "Get `pricing` for which there are at least `seats` empty seats available."
  (filter #(>= (second %) seats) pricing))

(defn lowest-available-price [flight seats]
  "Returns the lowest price in `flight` for which at least `seats` empty seats
  are available, or nil if none found."
  (-> (:pricing flight)                 ; [[price available taken]]
    (filter-pricing-with-n-seats seats)
    (sort-pricing)
    (first)                             ; [price available taken]
    (first)))                           ; price

(defn- find-flight [flights customer]
  "Find a flight in `flights` that is on the route and within the budget of
  `customer`. If a flight was found, returns {:flight flight :price price},
  else returns nil."
  (let [{:keys [_id from to seats budget]}
          customer
        flights-and-prices
          ; flights that are on the route and within budget, and their price
          (for [f flights
                :when (and (= (:from f) from) (= (:to f) to))
                :let [lowest-price (lowest-available-price f seats)]
                :when (and (some? lowest-price) (<= lowest-price budget))]
            {:flight f :price lowest-price})
        cheapest-flight-and-price
          (first (sort-by :price flights-and-prices))]
    cheapest-flight-and-price))

(defn- book [flight price seats]
  "Updates `flight` to book `seats` at `price`."
  (update flight :pricing
    (fn [pricing]
      (for [[p a t] pricing]
        (if (= p price)
          [p (- a seats) (+ t seats)]
          [p a t])))))

(defn- process-customer [flights customer]
  "Try to book a flight from `flights` for `customer`, returning the updated
  flight if found, or nil if no suitable flight was found."
  (if-let [{:keys [flight price]} (find-flight flights customer)]
    (let [updated-flight (book flight price (:seats customer))]
      (log "Customer" (:id customer) "booked" (:seats customer)
        "seats on flight" (:id updated-flight) "at $" price " (< budget of $"
        (:budget customer) ").")
      updated-flight)
    (do
      (log "Customer" (:id customer) "did not find a flight.")
      nil)))

(def finished-processing?
  "Set to true once all customers have been processed, so that sales process
  can end."
  (atom false))

(defn process-customers [customers]
  "Process `customers` one by one."
  (Thread/sleep 100)
  (dosync
  (doseq [customer customers]

    (alter flights
      (fn [flights]
        (if-let [updated-flight (process-customer flights customer)]
          (assoc flights (:id updated-flight) updated-flight)
          flights)))))
  (reset! finished-processing? true))

(defn sales-process []
  "The sales process starts and ends sales periods, until `finished-processing?`
  is true."
  (loop []
    (let [discounted-flight-ids (->> input/flights
                                     (pmap :id)
                                     shuffle
                                     (take input/NUMBER_OF_DISCOUNTED_FLIGHTS)
                                     set)]
      (Thread/sleep input/TIME_BETWEEN_SALES)
      (start-sale discounted-flight-ids)
      (Thread/sleep input/TIME_OF_SALES)
      (end-sale discounted-flight-ids)
    (if (not @finished-processing?)
      (recur)))))

(defn partitionCustomerInput 
  [threads customers]
  (let [partitions (partition-all 
     (Math/ceil (/ (count customers) threads))  customers)]
        partitions))

(defn main []
  (initialize-flights input/flights)
  (let [f1 (time (doall (pmap process-customers (partitionCustomerInput N-THREADS input/customers))))
        f2 (future (sales-process))]






    @f2)
  (println "Flights:")
  (print-flights @flights))

(main)
(shutdown-agents)

顺序实现比并行实现更快。我不明白为什么在并行实现中,在main中,在将客户集合拆分为等于所需线程数的分区后,我将客户处理与pmap并行化。

1 个答案:

答案 0 :(得分:0)

并行性和pmap的问题是它仍然保证返回值排序。这意味着如果你有一个包含8个核心/工作人员的pmap池和一个块,那么整个批次将被阻止。

看看Claypoole的无序pmap'uppmap',其中返回值排序被删除,有利于更快的返回值。在这里,一个阻塞操作仍然会留下7个内核以实现更快的操作。