clojure - ¿Se puede monitorear el nivel de contención de STM?
(3)
¿Hay alguna manera de sondear si las transacciones STM de Clojure están siendo reintentadas, y a qué velocidad?
Al introducir los bloques dosync nombrados y los recuentos de compromiso (las veces que una dosync nombrada ha tenido éxito), uno puede fácilmente realizar un seguimiento de los tiempos que los hilos han reintentado una transacción determinada.
(def ^{:doc "ThreadLocal<Map<TxName, Map<CommitNumber, TriesCount>>>"}
local-tries (let [l (ThreadLocal.)]
(.set l {})
l))
(def ^{:doc "Map<TxName, Int>"}
commit-number (ref {}))
(def history ^{:doc "Map<ThreadId, Map<TxName, Map<CommitNumber, TriesCount>>>"}
(atom {}))
(defn report [_ thread-id tries]
(swap! history assoc thread-id tries))
(def reporter (agent nil))
(defmacro dosync [tx-name & body]
`(clojure.core/dosync
(let [cno# (@commit-number ~tx-name 0)
tries# (update-in (.get local-tries) [~tx-name] update-in [cno#] (fnil inc 0))]
(.set local-tries tries#)
(send reporter report (.getId (Thread/currentThread)) tries#))
~@body
(alter commit-number update-in [~tx-name] (fnil inc 0))))
Dado el siguiente ejemplo ...
(def foo (ref {}))
(def bar (ref {}))
(defn x []
(dosync :x ;; `:x`: the tx-name.
(let [r (rand-int 2)]
(alter foo assoc r (rand))
(Thread/sleep (rand-int 400))
(alter bar assoc (rand-int 2) (@foo r)))))
(dotimes [i 4]
(future
(dotimes [i 10]
(x))))
... @history
evalúa a:
;; {thread-id {tx-name {commit-number tries-count}}}
{40 {:x {3 1, 2 4, 1 3, 0 1}}, 39 {:x {2 1, 1 3, 0 1}}, ...}
Esta implementación adicional es sustancialmente más simple.
;; {thread-id retries-of-latest-tx}
(def tries (atom {}))
;; The max amount of tries any thread has performed
(def max-tries (atom 0))
(def ninc (fnil inc 0))
(def reporter (agent nil))
(defn report [_ tid]
(swap! max-tries #(max % (get @tries tid 0)))
(swap! tries update-in [tid] (constantly 0)))
(defmacro dosync [& body]
`(clojure.core/dosync
(swap! tries update-in [(.getId (Thread/currentThread))] ninc)
(commute commit-id inc)
(send reporter report (.getId (Thread/currentThread)))
~@body))
Puede observar el history count
de una referencia que indicará que hay una disputa sobre ella:
user=> (def my-ref (ref 0 :min-history 1))
#''user/my-ref
user=> (ref-history-count my-ref)
0
user=> (dosync (alter my-ref inc))
1
user=> (ref-history-count my-ref)
1
El recuento de historial no representa directamente la contención. En su lugar, representa la cantidad de valores pasados que se han mantenido para dar servicio a las lecturas concurrentes.
El tamaño del historial está limitado por valores mínimos y max
. Por defecto, esos son 0
y 10
, respectivamente, pero puede cambiarlos al crear el ref
(ver arriba). Dado que min-history
es 0
de forma predeterminada, normalmente no verá los valores de devolución de ref-history-count
no sean cero, a menos que haya contención en la ref.
Consulte más información sobre el history count
aquí: https://groups.google.com/forum/?fromgroups#!topic/clojure/n_MKCoa870o
No creo que haya ninguna manera, proporcionada por clojure.core
, de observar la tasa de transacciones de STM en este momento. Por supuesto, puede hacer algo similar a lo que hizo @Chouser en su prueba de estrés de la historia :
(dosync
(swap! try-count inc)
...)
es decir, incrementar un contador dentro de la transacción. El incremento ocurrirá cada vez que se intente la transacción. Si try-count
es mayor que 1
, se reintentó la transacción.