From patchwork Thu Aug 27 17:35:16 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?b?RWR3aW4gVMO2csO2aw==?= X-Patchwork-Id: 11741189 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 97B1C14F6 for ; Thu, 27 Aug 2020 17:37:13 +0000 (UTC) Received: from lists.xenproject.org (lists.xenproject.org [192.237.175.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 71AC22087E for ; Thu, 27 Aug 2020 17:37:13 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=citrix.com header.i=@citrix.com header.b="NcLTYFwK" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 71AC22087E Authentication-Results: mail.kernel.org; dmarc=fail (p=reject dis=none) header.from=citrix.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=xen-devel-bounces@lists.xenproject.org Received: from localhost ([127.0.0.1] helo=lists.xenproject.org) by lists.xenproject.org with esmtp (Exim 4.92) (envelope-from ) id 1kBLoK-0004sA-Dc; Thu, 27 Aug 2020 17:35:40 +0000 Received: from us1-rack-iad1.inumbo.com ([172.99.69.81]) by lists.xenproject.org with esmtp (Exim 4.92) (envelope-from ) id 1kBLoJ-0004rt-0G for xen-devel@lists.xenproject.org; Thu, 27 Aug 2020 17:35:39 +0000 X-Inumbo-ID: d5f92419-806d-49ae-ab8d-ec591510ac8c Received: from esa6.hc3370-68.iphmx.com (unknown [216.71.155.175]) by us1-rack-iad1.inumbo.com (Halon) with ESMTPS id d5f92419-806d-49ae-ab8d-ec591510ac8c; Thu, 27 Aug 2020 17:35:36 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=citrix.com; s=securemail; t=1598549736; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=vBweS9ARRS6dGZnE0Pno7QFmEtzlWtvl0lTYI1i4qLM=; b=NcLTYFwKTTVN7efNO5KQP2G7yIIXzQrwLXHnFj2ONEio8/qYkff3MCht dCVVyAbq9TXhzpCB7KMBoYMBVPvqPAsMIxo48Lc54Es34XGNiRjsHWBjZ VY9jLHwrmkgvhaPPujAZti5Z+UTw9lIsFvWzH7pdZj1f3rJZclIV3IJv0 g=; Authentication-Results: esa6.hc3370-68.iphmx.com; dkim=none (message not signed) header.i=none IronPort-SDR: QuUjqSh4oHQ6i77hJJ63UTqGU4m8OmnTRH1seqh7JM0eTEmZ/yn7CaKHBmQoFqaIM/h1zJJQXx r6PvK9ECw7jYQET3aHDQv8EsLod1hHlQeHGyV3CpvU/BAXJWlAua+PXt2Av8hMbreScy3PIMRE mEWjL9Qmw5j5TzAO9fSNM8MdDlmC00CfoJKZ05YY+j+aSR0u7Xukm1Uywn6y6oU7970GotXgNU 34QFMCHB5jxM1IAsLzgOfIdM5oqpZMVNzE2CMPTDY/EAACMRH+pzcd1ojxESJS7E4yZQZWUqUD qfo= X-SBRS: 2.7 X-MesageID: 25758460 X-Ironport-Server: esa6.hc3370-68.iphmx.com X-Remote-IP: 162.221.158.21 X-Policy: $RELAYED X-IronPort-AV: E=Sophos;i="5.76,360,1592884800"; d="scan'208";a="25758460" From: =?utf-8?b?RWR3aW4gVMO2csO2aw==?= To: CC: =?utf-8?b?RWR3aW4gVMO2csO2aw==?= , "Christian Lindig" , David Scott , "Ian Jackson" , Wei Liu Subject: [PATCH v4 1/4] tools/ocaml/xenstored: replace hand rolled GC with weak GC references Date: Thu, 27 Aug 2020 18:35:16 +0100 Message-ID: X-Mailer: git-send-email 2.25.1 In-Reply-To: References: MIME-Version: 1.0 X-BeenThere: xen-devel@lists.xenproject.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Xen developer discussion List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , Errors-To: xen-devel-bounces@lists.xenproject.org Sender: "Xen-devel" The code here is attempting to reduce memory usage by sharing common substrings in the tree: it replaces strings with ints, and keeps a string->int map that gets manually garbage collected using a hand-rolled mark and sweep algorithm. This is unnecessary: OCaml already has a mark-and-sweep Garbage Collector runtime, and sharing of common strings in tree nodes can be achieved through Weak references: if the string hasn't been seen yet it gets added to the Weak reference table, and if it has we use the entry from the table instead, thus storing a string only once. When the string is no longer referenced OCaml's GC will drop it from the weak table: there is no need to manually do a mark-and-sweep, or to tell OCaml when to drop it. Signed-off-by: Edwin Török Acked-by: Christian Lindig --- Changes since V3: * replace String.equal with (=) for compatibility with 4.02 --- tools/ocaml/xenstored/connection.ml | 3 -- tools/ocaml/xenstored/history.ml | 14 ------ tools/ocaml/xenstored/store.ml | 11 ++--- tools/ocaml/xenstored/symbol.ml | 68 ++++++----------------------- tools/ocaml/xenstored/symbol.mli | 21 ++------- tools/ocaml/xenstored/xenstored.ml | 16 +------ 6 files changed, 24 insertions(+), 109 deletions(-) diff --git a/tools/ocaml/xenstored/connection.ml b/tools/ocaml/xenstored/connection.ml index 24750ada43..aa6dd95501 100644 --- a/tools/ocaml/xenstored/connection.ml +++ b/tools/ocaml/xenstored/connection.ml @@ -271,9 +271,6 @@ let has_more_work con = let incr_ops con = con.stat_nb_ops <- con.stat_nb_ops + 1 -let mark_symbols con = - Hashtbl.iter (fun _ t -> Store.mark_symbols (Transaction.get_store t)) con.transactions - let stats con = Hashtbl.length con.watches, con.stat_nb_ops diff --git a/tools/ocaml/xenstored/history.ml b/tools/ocaml/xenstored/history.ml index f39565bff5..029802bd15 100644 --- a/tools/ocaml/xenstored/history.ml +++ b/tools/ocaml/xenstored/history.ml @@ -22,20 +22,6 @@ type history_record = { let history : history_record list ref = ref [] -(* Called from periodic_ops to ensure we don't discard symbols that are still needed. *) -(* There is scope for optimisation here, since in consecutive commits one commit's `after` - * is the same thing as the next commit's `before`, but not all commits in history are - * consecutive. *) -let mark_symbols () = - (* There are gaps where dom0's commits are missing. Otherwise we could assume that - * each element's `before` is the same thing as the next element's `after` - * since the next element is the previous commit *) - List.iter (fun hist_rec -> - Store.mark_symbols hist_rec.before; - Store.mark_symbols hist_rec.after; - ) - !history - (* Keep only enough commit-history to protect the running transactions that we are still tracking *) (* There is scope for optimisation here, replacing List.filter with something more efficient, * probably on a different list-like structure. *) diff --git a/tools/ocaml/xenstored/store.ml b/tools/ocaml/xenstored/store.ml index f299ec6461..45659a23ee 100644 --- a/tools/ocaml/xenstored/store.ml +++ b/tools/ocaml/xenstored/store.ml @@ -46,18 +46,18 @@ let add_child node child = let exists node childname = let childname = Symbol.of_string childname in - List.exists (fun n -> n.name = childname) node.children + List.exists (fun n -> Symbol.equal n.name childname) node.children let find node childname = let childname = Symbol.of_string childname in - List.find (fun n -> n.name = childname) node.children + List.find (fun n -> Symbol.equal n.name childname) node.children let replace_child node child nchild = (* this is the on-steroid version of the filter one-replace one *) let rec replace_one_in_list l = match l with | [] -> [] - | h :: tl when h.name = child.name -> nchild :: tl + | h :: tl when Symbol.equal h.name child.name -> nchild :: tl | h :: tl -> h :: replace_one_in_list tl in { node with children = (replace_one_in_list node.children) } @@ -67,7 +67,7 @@ let del_childname node childname = let rec delete_one_in_list l = match l with | [] -> raise Not_found - | h :: tl when h.name = sym -> tl + | h :: tl when Symbol.equal h.name sym -> tl | h :: tl -> h :: delete_one_in_list tl in { node with children = (delete_one_in_list node.children) } @@ -463,9 +463,6 @@ let copy store = { quota = Quota.copy store.quota; } -let mark_symbols store = - Node.recurse (fun node -> Symbol.mark_as_used node.Node.name) store.root - let incr_transaction_coalesce store = store.stat_transaction_coalesce <- store.stat_transaction_coalesce + 1 let incr_transaction_abort store = diff --git a/tools/ocaml/xenstored/symbol.ml b/tools/ocaml/xenstored/symbol.ml index 4420c6a4d7..2b41d120f6 100644 --- a/tools/ocaml/xenstored/symbol.ml +++ b/tools/ocaml/xenstored/symbol.ml @@ -14,63 +14,23 @@ * GNU Lesser General Public License for more details. *) -type t = int +module WeakTable = Weak.Make(struct + type t = string + let equal (x:string) (y:string) = (x = y) + let hash = Hashtbl.hash +end) -type 'a record = { data: 'a; mutable garbage: bool } -let int_string_tbl : (int,string record) Hashtbl.t = Hashtbl.create 1024 -let string_int_tbl : (string,int) Hashtbl.t = Hashtbl.create 1024 +type t = string -let created_counter = ref 0 -let used_counter = ref 0 +let tbl = WeakTable.create 1024 -let count = ref 0 -let rec fresh () = - if Hashtbl.mem int_string_tbl !count - then begin - incr count; - fresh () - end else - !count +let of_string s = WeakTable.merge tbl s +let to_string s = s -let new_record v = { data=v; garbage=false } - -let of_string name = - if Hashtbl.mem string_int_tbl name - then begin - incr used_counter; - Hashtbl.find string_int_tbl name - end else begin - let i = fresh () in - incr created_counter; - Hashtbl.add string_int_tbl name i; - Hashtbl.add int_string_tbl i (new_record name); - i - end - -let to_string i = - (Hashtbl.find int_string_tbl i).data - -let mark_all_as_unused () = - Hashtbl.iter (fun _ v -> v.garbage <- true) int_string_tbl - -let mark_as_used symb = - let record1 = Hashtbl.find int_string_tbl symb in - record1.garbage <- false - -let garbage () = - let records = Hashtbl.fold (fun symb record accu -> - if record.garbage then (symb, record.data) :: accu else accu - ) int_string_tbl [] in - let remove (int,string) = - Hashtbl.remove int_string_tbl int; - Hashtbl.remove string_int_tbl string - in - created_counter := 0; - used_counter := 0; - List.iter remove records +let equal a b = + (* compare using physical equality, both members have to be part of the above weak table *) + a == b let stats () = - Hashtbl.length string_int_tbl - -let created () = !created_counter -let used () = !used_counter + let len, entries, _, _, _, _ = WeakTable.stats tbl in + len, entries diff --git a/tools/ocaml/xenstored/symbol.mli b/tools/ocaml/xenstored/symbol.mli index c3c9f6e2f8..586ab57507 100644 --- a/tools/ocaml/xenstored/symbol.mli +++ b/tools/ocaml/xenstored/symbol.mli @@ -29,24 +29,11 @@ val of_string : string -> t val to_string : t -> string (** Convert a symbol into a string. *) -(** {6 Garbage Collection} *) - -(** Symbols need to be regulary garbage collected. The following steps should be followed: -- mark all the knowns symbols as unused (with [mark_all_as_unused]); -- mark all the symbols really usefull as used (with [mark_as_used]); and -- finally, call [garbage] *) - -val mark_all_as_unused : unit -> unit -val mark_as_used : t -> unit -val garbage : unit -> unit +val equal: t -> t -> bool +(** Compare two symbols for equality *) (** {6 Statistics } *) -val stats : unit -> int -(** Get the number of used symbols. *) +val stats : unit -> int * int +(** Get the table size and number of entries. *) -val created : unit -> int -(** Returns the number of symbols created since the last GC. *) - -val used : unit -> int -(** Returns the number of existing symbols used since the last GC *) diff --git a/tools/ocaml/xenstored/xenstored.ml b/tools/ocaml/xenstored/xenstored.ml index 5b96f1852a..f3e4697dea 100644 --- a/tools/ocaml/xenstored/xenstored.ml +++ b/tools/ocaml/xenstored/xenstored.ml @@ -376,18 +376,6 @@ let _ = let periodic_ops now = debug "periodic_ops starting"; - (* we garbage collect the string->int dictionary after a sizeable amount of operations, - * there's no need to be really fast even if we got loose - * objects since names are often reuse. - *) - if Symbol.created () > 1000 || Symbol.used () > 20000 - then begin - Symbol.mark_all_as_unused (); - Store.mark_symbols store; - Connections.iter cons Connection.mark_symbols; - History.mark_symbols (); - Symbol.garbage () - end; (* scan all the xs rings as a safenet for ill-behaved clients *) if !ring_scan_interval >= 0 && now > (!last_scan_time +. float !ring_scan_interval) then @@ -405,11 +393,11 @@ let _ = let (lanon, lanon_ops, lanon_watchs, ldom, ldom_ops, ldom_watchs) = Connections.stats cons in let store_nodes, store_abort, store_coalesce = Store.stats store in - let symtbl_len = Symbol.stats () in + let symtbl_len, symtbl_entries = Symbol.stats () in info "store stat: nodes(%d) t-abort(%d) t-coalesce(%d)" store_nodes store_abort store_coalesce; - info "sytbl stat: %d" symtbl_len; + info "sytbl stat: length(%d) entries(%d)" symtbl_len symtbl_entries; info " con stat: anonymous(%d, %d o, %d w) domains(%d, %d o, %d w)" lanon lanon_ops lanon_watchs ldom ldom_ops ldom_watchs; info " mem stat: minor(%.0f) promoted(%.0f) major(%.0f) heap(%d w, %d c) live(%d w, %d b) free(%d w, %d b)" From patchwork Thu Aug 27 17:35:17 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?b?RWR3aW4gVMO2csO2aw==?= X-Patchwork-Id: 11741179 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id E1D15109B for ; Thu, 27 Aug 2020 17:36:42 +0000 (UTC) Received: from lists.xenproject.org (lists.xenproject.org [192.237.175.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id B9E792087E for ; Thu, 27 Aug 2020 17:36:42 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=citrix.com header.i=@citrix.com header.b="PhaJ1RBo" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org B9E792087E Authentication-Results: mail.kernel.org; dmarc=fail (p=reject dis=none) header.from=citrix.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=xen-devel-bounces@lists.xenproject.org Received: from localhost ([127.0.0.1] helo=lists.xenproject.org) by lists.xenproject.org with esmtp (Exim 4.92) (envelope-from ) id 1kBLoO-0004sp-U8; Thu, 27 Aug 2020 17:35:44 +0000 Received: from us1-rack-iad1.inumbo.com ([172.99.69.81]) by lists.xenproject.org with esmtp (Exim 4.92) (envelope-from ) id 1kBLoO-0004rt-2A for xen-devel@lists.xenproject.org; Thu, 27 Aug 2020 17:35:44 +0000 X-Inumbo-ID: cf6abffc-1cb1-40b9-ba43-eccd69afc856 Received: from esa6.hc3370-68.iphmx.com (unknown [216.71.155.175]) by us1-rack-iad1.inumbo.com (Halon) with ESMTPS id cf6abffc-1cb1-40b9-ba43-eccd69afc856; Thu, 27 Aug 2020 17:35:38 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=citrix.com; s=securemail; t=1598549738; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=7hWEapjYocjJvSlH395vbK2zM9LXuSeRl6Q8PsG4SWY=; b=PhaJ1RBoIwgkz8BU0P5+8JI8BzYZ7drQoJuTavoTUhetmi6YxVKu5Fkf WnHOHUd41wqCkmt3L/IMLA6oskHRfIfbD8+IB7AEHD1uMHB6fdqBvqy7Z WgkdSqd6PFxkV/hGbdd1Son1k5eC4MicmH2PlVVj6X9eWt388joZzjKFd g=; Authentication-Results: esa6.hc3370-68.iphmx.com; dkim=none (message not signed) header.i=none IronPort-SDR: XxqxiNINzuojLKJrSIi/92+CJfnfQQTe/l7R50MqeOiA6jEbNuwGnnvNH87L/FTplRj8cA2had FNkbAyNJ+POufE/8W/eSirwgSaSt7hpGZ/4lRz5PXlAVdf9D7xtt8OWRbvHmljS3iFGqk3u5SC fs+jYp9WG5QohanzX5UkrVBx8Xtp0med6+5dHvpnGLQ0VAuV2h/sdj99y04ZqqWl723CNwqzo/ x8XwWget1+imzOzckVFS99RMzMXz0+hiObq132zrI+lQqCScGQ43wKVXbL5LNUAkTCSO5ZiFCY 0wE= X-SBRS: 2.7 X-MesageID: 25758462 X-Ironport-Server: esa6.hc3370-68.iphmx.com X-Remote-IP: 162.221.158.21 X-Policy: $RELAYED X-IronPort-AV: E=Sophos;i="5.76,360,1592884800"; d="scan'208";a="25758462" From: =?utf-8?b?RWR3aW4gVMO2csO2aw==?= To: CC: =?utf-8?b?RWR3aW4gVMO2csO2aw==?= , "Christian Lindig" , David Scott , "Ian Jackson" , Wei Liu Subject: [PATCH v4 2/4] Map: backport find_opt/update from 4.06 Date: Thu, 27 Aug 2020 18:35:17 +0100 Message-ID: <72b1f39ce900870819630cc7ba5bcb1f6610de77.1598548188.git.edvin.torok@citrix.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: References: MIME-Version: 1.0 X-BeenThere: xen-devel@lists.xenproject.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Xen developer discussion List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , Errors-To: xen-devel-bounces@lists.xenproject.org Sender: "Xen-devel" We are currently on OCaml 4.02 as minimum version. To make the followup optimizations compile backport these functions from OCaml 4.06. This implementation is less efficient than the one in the 4.06 standard library which has access to the internals of the Map. Signed-off-by: Edwin Török --- Changes since V3: * this patch is new in V4 --- tools/ocaml/xenstored/stdext.ml | 21 +++++++++++++++++++++ tools/ocaml/xenstored/trie.ml | 2 ++ 2 files changed, 23 insertions(+) diff --git a/tools/ocaml/xenstored/stdext.ml b/tools/ocaml/xenstored/stdext.ml index 4f2f3a2c8c..5bebe2aa27 100644 --- a/tools/ocaml/xenstored/stdext.ml +++ b/tools/ocaml/xenstored/stdext.ml @@ -44,6 +44,27 @@ let default d v = let maybe f v = match v with None -> () | Some x -> f x +module Map = struct +module Make(Ord: Map.OrderedType) = struct + +include Map.Make(Ord) + +let find_opt k t = + (* avoid raising exceptions, they can be expensive *) + if mem k t then Some (find k t) else None + +let update k f t = + let r = find_opt k t in + let r' = f r in + match r, r' with + | None, None -> t + | Some _, None -> remove k t + | Some r, Some r' when r == r' -> t + | _, Some r' -> add k r' t + +end +end + module String = struct include String let of_char c = String.make 1 c diff --git a/tools/ocaml/xenstored/trie.ml b/tools/ocaml/xenstored/trie.ml index dc42535092..f513f4e608 100644 --- a/tools/ocaml/xenstored/trie.ml +++ b/tools/ocaml/xenstored/trie.ml @@ -13,6 +13,8 @@ * GNU Lesser General Public License for more details. *) +open Stdext + module Node = struct type ('a,'b) t = { From patchwork Thu Aug 27 17:35:18 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?b?RWR3aW4gVMO2csO2aw==?= X-Patchwork-Id: 11741185 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 241F91575 for ; Thu, 27 Aug 2020 17:37:00 +0000 (UTC) Received: from lists.xenproject.org (lists.xenproject.org [192.237.175.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id F20352087E for ; Thu, 27 Aug 2020 17:36:59 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=citrix.com header.i=@citrix.com header.b="X657iDqq" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org F20352087E Authentication-Results: mail.kernel.org; dmarc=fail (p=reject dis=none) header.from=citrix.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=xen-devel-bounces@lists.xenproject.org Received: from localhost ([127.0.0.1] helo=lists.xenproject.org) by lists.xenproject.org with esmtp (Exim 4.92) (envelope-from ) id 1kBLoZ-0004vs-GH; Thu, 27 Aug 2020 17:35:55 +0000 Received: from us1-rack-iad1.inumbo.com ([172.99.69.81]) by lists.xenproject.org with esmtp (Exim 4.92) (envelope-from ) id 1kBLoY-0004rt-0t for xen-devel@lists.xenproject.org; Thu, 27 Aug 2020 17:35:54 +0000 X-Inumbo-ID: bdde6ff7-589b-4f51-91ca-6207053fe6c2 Received: from esa1.hc3370-68.iphmx.com (unknown [216.71.145.142]) by us1-rack-iad1.inumbo.com (Halon) with ESMTPS id bdde6ff7-589b-4f51-91ca-6207053fe6c2; Thu, 27 Aug 2020 17:35:40 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=citrix.com; s=securemail; t=1598549741; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=XJwKs8GU3g8Eh4fl7JRfuz1lXuRcd9stEXMwbGAxJPY=; b=X657iDqqo1PvLLuYh+s+uPDDuHacFiMs/Bq/GPai2ArEhCheAQDF/9Dn PtMCkb7bAX9nEdHa9UhFjB3+e1OqdK2ZOsYmVRjvRnJ2Mfoz5YW6cHBhP dotyfNamhauFbCkeTFIQc0ZY+c60SDdWXS0Z/JlNux/R2mVjoBnZz43Go o=; Authentication-Results: esa1.hc3370-68.iphmx.com; dkim=none (message not signed) header.i=none IronPort-SDR: z/6i34KxBdo+mzhD04RgI1iAlTWPZICXLgRkinBtxCc0mdxVFJ5hP5o0BcQiCEhkqEf9VsQn56 8aHE/6kjvqsstGA0iiytEnCwJX8rOfrq0HQgdt+7kqM2A5gOHK5R+JTTYqvcXQ6hfLywGfiHz1 1kGOPrOTm53Oh7868XzVfgTheikVUcrtpoR4sYhq4yDBqA0S08pRi49Ay3D7N/k6Y2p/wV8EZx XgXHHFKrsJmVT+Vx1nMRQ1WIVDdZ4YRbHpyVXCGai6rAzgNZ2NaQwqE4EUzKNjRcE1pHfnVaa/ ygo= X-SBRS: 2.7 X-MesageID: 25789167 X-Ironport-Server: esa1.hc3370-68.iphmx.com X-Remote-IP: 162.221.158.21 X-Policy: $RELAYED X-IronPort-AV: E=Sophos;i="5.76,360,1592884800"; d="scan'208";a="25789167" From: =?utf-8?b?RWR3aW4gVMO2csO2aw==?= To: CC: =?utf-8?b?RWR3aW4gVMO2csO2aw==?= , "Christian Lindig" , David Scott , "Ian Jackson" , Wei Liu Subject: [PATCH v4 3/4] tools/ocaml/xenstored: use more efficient node trees Date: Thu, 27 Aug 2020 18:35:18 +0100 Message-ID: X-Mailer: git-send-email 2.25.1 In-Reply-To: References: MIME-Version: 1.0 X-BeenThere: xen-devel@lists.xenproject.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Xen developer discussion List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , Errors-To: xen-devel-bounces@lists.xenproject.org Sender: "Xen-devel" This changes the output of xenstore-ls to be sorted. Previously the keys were listed in the order in which they were inserted in. docs/misc/xenstore.txt doesn't specify in what order keys are listed. Map.update is used to retain semantics with replace_child: only an existing child is replaced, if it wasn't part of the original map we don't add it. Similarly exception behaviour is retained for del_childname and related functions. Entries are stored in reverse sort order, so that upon Map.fold the constructed list is sorted in ascending order and there is no need for a List.rev. Signed-off-by: Edwin Török Acked-by: Christian Lindig --- Changes since V3: * none, repost after preceding commits fix OCaml 4.02 compatibility --- tools/ocaml/xenstored/store.ml | 46 +++++++++++++++----------------- tools/ocaml/xenstored/symbol.ml | 4 +++ tools/ocaml/xenstored/symbol.mli | 3 +++ 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/tools/ocaml/xenstored/store.ml b/tools/ocaml/xenstored/store.ml index 45659a23ee..d9dfa36045 100644 --- a/tools/ocaml/xenstored/store.ml +++ b/tools/ocaml/xenstored/store.ml @@ -16,17 +16,19 @@ *) open Stdext +module SymbolMap = Map.Make(Symbol) + module Node = struct type t = { name: Symbol.t; perms: Perms.Node.t; value: string; - children: t list; + children: t SymbolMap.t; } let create _name _perms _value = - { name = Symbol.of_string _name; perms = _perms; value = _value; children = []; } + { name = Symbol.of_string _name; perms = _perms; value = _value; children = SymbolMap.empty; } let get_owner node = Perms.Node.get_owner node.perms let get_children node = node.children @@ -42,38 +44,34 @@ let set_value node nvalue = let set_perms node nperms = { node with perms = nperms } let add_child node child = - { node with children = child :: node.children } + let children = SymbolMap.add child.name child node.children in + { node with children } let exists node childname = let childname = Symbol.of_string childname in - List.exists (fun n -> Symbol.equal n.name childname) node.children + SymbolMap.mem childname node.children let find node childname = let childname = Symbol.of_string childname in - List.find (fun n -> Symbol.equal n.name childname) node.children + SymbolMap.find childname node.children let replace_child node child nchild = - (* this is the on-steroid version of the filter one-replace one *) - let rec replace_one_in_list l = - match l with - | [] -> [] - | h :: tl when Symbol.equal h.name child.name -> nchild :: tl - | h :: tl -> h :: replace_one_in_list tl - in - { node with children = (replace_one_in_list node.children) } + { node with + children = SymbolMap.update child.name + (function None -> None | Some _ -> Some nchild) + node.children + } let del_childname node childname = let sym = Symbol.of_string childname in - let rec delete_one_in_list l = - match l with - | [] -> raise Not_found - | h :: tl when Symbol.equal h.name sym -> tl - | h :: tl -> h :: delete_one_in_list tl - in - { node with children = (delete_one_in_list node.children) } + { node with children = + SymbolMap.update sym + (function None -> raise Not_found | Some _ -> None) + node.children + } let del_all_children node = - { node with children = [] } + { node with children = SymbolMap.empty } (* check if the current node can be accessed by the current connection with rperm permissions *) let check_perm node connection request = @@ -87,7 +85,7 @@ let check_owner node connection = raise Define.Permission_denied; end -let rec recurse fct node = fct node; List.iter (recurse fct) node.children +let rec recurse fct node = fct node; SymbolMap.iter (fun _ -> recurse fct) node.children let unpack node = (Symbol.to_string node.name, node.perms, node.value) @@ -321,7 +319,7 @@ let ls store perm path = Node.check_perm cnode perm Perms.READ; cnode.Node.children in Path.apply store.root path do_ls in - List.rev (List.map (fun n -> Symbol.to_string n.Node.name) children) + SymbolMap.fold (fun k _ accu -> Symbol.to_string k :: accu) children [] let getperms store perm path = if path = [] then @@ -350,7 +348,7 @@ let traversal root_node f = let rec _traversal path node = f path node; let node_path = Path.of_path_and_name path (Symbol.to_string node.Node.name) in - List.iter (_traversal node_path) node.Node.children + SymbolMap.iter (fun _ -> _traversal node_path) node.Node.children in _traversal [] root_node diff --git a/tools/ocaml/xenstored/symbol.ml b/tools/ocaml/xenstored/symbol.ml index 2b41d120f6..301639f16f 100644 --- a/tools/ocaml/xenstored/symbol.ml +++ b/tools/ocaml/xenstored/symbol.ml @@ -31,6 +31,10 @@ let equal a b = (* compare using physical equality, both members have to be part of the above weak table *) a == b +let compare a b = + if equal a b then 0 + else -(String.compare a b) + let stats () = let len, entries, _, _, _, _ = WeakTable.stats tbl in len, entries diff --git a/tools/ocaml/xenstored/symbol.mli b/tools/ocaml/xenstored/symbol.mli index 586ab57507..dd0f014796 100644 --- a/tools/ocaml/xenstored/symbol.mli +++ b/tools/ocaml/xenstored/symbol.mli @@ -32,6 +32,9 @@ val to_string : t -> string val equal: t -> t -> bool (** Compare two symbols for equality *) +val compare: t -> t -> int +(** Compare two symbols *) + (** {6 Statistics } *) val stats : unit -> int * int From patchwork Thu Aug 27 17:35:19 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?b?RWR3aW4gVMO2csO2aw==?= X-Patchwork-Id: 11741181 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id DA3BF109B for ; Thu, 27 Aug 2020 17:36:47 +0000 (UTC) Received: from lists.xenproject.org (lists.xenproject.org [192.237.175.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id B2E0E2087E for ; Thu, 27 Aug 2020 17:36:46 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=citrix.com header.i=@citrix.com header.b="huqWs/pg" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org B2E0E2087E Authentication-Results: mail.kernel.org; dmarc=fail (p=reject dis=none) header.from=citrix.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=xen-devel-bounces@lists.xenproject.org Received: from localhost ([127.0.0.1] helo=lists.xenproject.org) by lists.xenproject.org with esmtp (Exim 4.92) (envelope-from ) id 1kBLoU-0004te-7o; Thu, 27 Aug 2020 17:35:50 +0000 Received: from us1-rack-iad1.inumbo.com ([172.99.69.81]) by lists.xenproject.org with esmtp (Exim 4.92) (envelope-from ) id 1kBLoT-0004rt-0h for xen-devel@lists.xenproject.org; Thu, 27 Aug 2020 17:35:49 +0000 X-Inumbo-ID: 87a7c698-b623-4679-bdc7-01697699bc48 Received: from esa5.hc3370-68.iphmx.com (unknown [216.71.155.168]) by us1-rack-iad1.inumbo.com (Halon) with ESMTPS id 87a7c698-b623-4679-bdc7-01697699bc48; Thu, 27 Aug 2020 17:35:39 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=citrix.com; s=securemail; t=1598549739; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=LOiqx3AIUR2H8keS52zJnePRPAXDNeq7GY6NRqun55w=; b=huqWs/pguiiLoh089pnoc4IvhoEZrXyD/LS2kadz/peDPfXYK+YBbPm2 clIwmAf2XB2yf88L7yFtMPbl7qPhQJ7Oq/01biCen2R+38/wZn8eO4oFb qzM++817n7QFSp1QaY/JNLPG7lR+CZza7iLjR0+ITJEHwdXlq9iMgu5i+ U=; Authentication-Results: esa5.hc3370-68.iphmx.com; dkim=none (message not signed) header.i=none IronPort-SDR: 9TNljb/lVfCdO5iDLYZyxkCmZZYSZaFzS8z247OiS2XCqnUrEdHg2/1QOFfSsZD04Fv8L8fNA5 CsWm2p+siO1CSNLfKVNTT4waQETfCvdR2KqsVnwOhIxS2MDaeS4C09OStkUbOb/V2iEMMnlHIv BhOFRKMUcXu9pnvKNsiK66kPOMJvfbMqN+OJkB31lb4QjxaCqStnbHg7rUZhqvDL7juFuEPw+r d92hysV0N07aGlpjnnn0AVCD9XPtcNLbPpUvMRyFQH63hQhj2zotydkTAofCv3cM1KVv3cEFMq nz0= X-SBRS: 2.7 X-MesageID: 25595231 X-Ironport-Server: esa5.hc3370-68.iphmx.com X-Remote-IP: 162.221.158.21 X-Policy: $RELAYED X-IronPort-AV: E=Sophos;i="5.76,360,1592884800"; d="scan'208";a="25595231" From: =?utf-8?b?RWR3aW4gVMO2csO2aw==?= To: CC: =?utf-8?b?RWR3aW4gVMO2csO2aw==?= , "Christian Lindig" , David Scott , "Ian Jackson" , Wei Liu Subject: [PATCH v4 4/4] tools/ocaml/xenstored: use more efficient tries Date: Thu, 27 Aug 2020 18:35:19 +0100 Message-ID: <483c350f48699b2cd4007ee2b6ba156f943102b1.1598548188.git.edvin.torok@citrix.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: References: MIME-Version: 1.0 X-BeenThere: xen-devel@lists.xenproject.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Xen developer discussion List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , Errors-To: xen-devel-bounces@lists.xenproject.org Sender: "Xen-devel" No functional change, just an optimization. Signed-off-by: Edwin Török Acked-by: Christian Lindig --- Changes since V3: * none, repost after previous commits fix compatibility with OCaml 4.02 --- tools/ocaml/xenstored/connections.ml | 2 +- tools/ocaml/xenstored/symbol.ml | 6 +-- tools/ocaml/xenstored/trie.ml | 59 ++++++++++++---------------- tools/ocaml/xenstored/trie.mli | 26 ++++++------ 4 files changed, 43 insertions(+), 50 deletions(-) diff --git a/tools/ocaml/xenstored/connections.ml b/tools/ocaml/xenstored/connections.ml index f02ef6b526..4983c7370b 100644 --- a/tools/ocaml/xenstored/connections.ml +++ b/tools/ocaml/xenstored/connections.ml @@ -21,7 +21,7 @@ type t = { anonymous: (Unix.file_descr, Connection.t) Hashtbl.t; domains: (int, Connection.t) Hashtbl.t; ports: (Xeneventchn.t, Connection.t) Hashtbl.t; - mutable watches: (string, Connection.watch list) Trie.t; + mutable watches: Connection.watch list Trie.t; } let create () = { diff --git a/tools/ocaml/xenstored/symbol.ml b/tools/ocaml/xenstored/symbol.ml index 301639f16f..72a84ebf80 100644 --- a/tools/ocaml/xenstored/symbol.ml +++ b/tools/ocaml/xenstored/symbol.ml @@ -31,9 +31,9 @@ let equal a b = (* compare using physical equality, both members have to be part of the above weak table *) a == b -let compare a b = - if equal a b then 0 - else -(String.compare a b) +(* the sort order is reversed here, so that Map.fold constructs a list + in ascending order *) +let compare a b = String.compare b a let stats () = let len, entries, _, _, _, _ = WeakTable.stats tbl in diff --git a/tools/ocaml/xenstored/trie.ml b/tools/ocaml/xenstored/trie.ml index f513f4e608..ad2aed5123 100644 --- a/tools/ocaml/xenstored/trie.ml +++ b/tools/ocaml/xenstored/trie.ml @@ -15,24 +15,26 @@ open Stdext +module StringMap = Map.Make(String) + module Node = struct - type ('a,'b) t = { - key: 'a; - value: 'b option; - children: ('a,'b) t list; + type 'a t = { + key: string; + value: 'a option; + children: 'a t StringMap.t; } let _create key value = { key = key; value = Some value; - children = []; + children = StringMap.empty; } let empty key = { key = key; value = None; - children = [] + children = StringMap.empty; } let _get_key node = node.key @@ -49,41 +51,31 @@ struct { node with children = children } let _add_child node child = - { node with children = child :: node.children } + { node with children = StringMap.add child.key child node.children } end -type ('a,'b) t = ('a,'b) Node.t list +type 'a t = 'a Node.t StringMap.t let mem_node nodes key = - List.exists (fun n -> n.Node.key = key) nodes + StringMap.mem key nodes let find_node nodes key = - List.find (fun n -> n.Node.key = key) nodes + StringMap.find key nodes let replace_node nodes key node = - let rec aux = function - | [] -> [] - | h :: tl when h.Node.key = key -> node :: tl - | h :: tl -> h :: aux tl - in - aux nodes + StringMap.update key (function None -> None | Some _ -> Some node) nodes let remove_node nodes key = - let rec aux = function - | [] -> raise Not_found - | h :: tl when h.Node.key = key -> tl - | h :: tl -> h :: aux tl - in - aux nodes + StringMap.update key (function None -> raise Not_found | Some _ -> None) nodes -let create () = [] +let create () = StringMap.empty let rec iter f tree = - let aux node = - f node.Node.key node.Node.value; + let aux key node = + f key node.Node.value; iter f node.Node.children in - List.iter aux tree + StringMap.iter aux tree let rec map f tree = let aux node = @@ -94,13 +86,14 @@ let rec map f tree = in { node with Node.value = value; Node.children = map f node.Node.children } in - List.filter (fun n -> n.Node.value <> None || n.Node.children <> []) (List.map aux tree) + tree |> StringMap.map aux + |> StringMap.filter (fun _ n -> n.Node.value <> None || not (StringMap.is_empty n.Node.children) ) let rec fold f tree acc = - let aux accu node = - fold f node.Node.children (f node.Node.key node.Node.value accu) + let aux key node accu = + fold f node.Node.children (f key node.Node.value accu) in - List.fold_left aux acc tree + StringMap.fold aux tree acc (* return a sub-trie *) let rec sub_node tree = function @@ -117,7 +110,7 @@ let rec sub_node tree = function let sub tree path = try (sub_node tree path).Node.children - with Not_found -> [] + with Not_found -> StringMap.empty let find tree path = Node.get_value (sub_node tree path) @@ -161,7 +154,7 @@ and set tree path value = replace_node tree h (set_node node t value) end else begin let node = Node.empty h in - set_node node t value :: tree + StringMap.add node.Node.key (set_node node t value) tree end let rec unset tree = function @@ -176,7 +169,7 @@ let rec unset tree = function then Node.set_children (Node.empty h) children else Node.set_children node children in - if children = [] && new_node.Node.value = None + if StringMap.is_empty children && new_node.Node.value = None then remove_node tree h else replace_node tree h new_node end else diff --git a/tools/ocaml/xenstored/trie.mli b/tools/ocaml/xenstored/trie.mli index 5dc53c1cb1..27785154f5 100644 --- a/tools/ocaml/xenstored/trie.mli +++ b/tools/ocaml/xenstored/trie.mli @@ -15,46 +15,46 @@ (** Basic Implementation of polymorphic tries (ie. prefix trees) *) -type ('a, 'b) t -(** The type of tries. ['a list] is the type of keys, ['b] the type of values. +type 'a t +(** The type of tries. ['a] the type of values. Internally, a trie is represented as a labeled tree, where node contains values - of type ['a * 'b option]. *) + of type [string * 'a option]. *) -val create : unit -> ('a,'b) t +val create : unit -> 'a t (** Creates an empty trie. *) -val mem : ('a,'b) t -> 'a list -> bool +val mem : 'a t -> string list -> bool (** [mem t k] returns true if a value is associated with the key [k] in the trie [t]. Otherwise, it returns false. *) -val find : ('a, 'b) t -> 'a list -> 'b +val find : 'a t -> string list -> 'a (** [find t k] returns the value associated with the key [k] in the trie [t]. Returns [Not_found] if no values are associated with [k] in [t]. *) -val set : ('a, 'b) t -> 'a list -> 'b -> ('a, 'b) t +val set : 'a t -> string list -> 'a -> 'a t (** [set t k v] associates the value [v] with the key [k] in the trie [t]. *) -val unset : ('a, 'b) t -> 'a list -> ('a, 'b) t +val unset : 'a t -> string list -> 'a t (** [unset k v] removes the association of value [v] with the key [k] in the trie [t]. Moreover, it automatically clean the trie, ie. it removes recursively every nodes of [t] containing no values and having no chil. *) -val iter : ('a -> 'b option -> unit) -> ('a, 'b) t -> unit +val iter : (string -> 'a option -> unit) -> 'a t -> unit (** [iter f t] applies the function [f] to every node of the trie [t]. As nodes of the trie [t] do not necessary contains a value, the second argument of [f] is an option type. *) -val iter_path : ('a -> 'b option -> unit) -> ('a, 'b) t -> 'a list -> unit +val iter_path : (string -> 'a option -> unit) -> 'a t -> string list -> unit (** [iter_path f t p] iterates [f] over nodes associated with the path [p] in the trie [t]. If [p] is not a valid path of [t], it iterates on the longest valid prefix of [p]. *) -val fold : ('a -> 'b option -> 'c -> 'c) -> ('a, 'b) t -> 'c -> 'c +val fold : (string -> 'a option -> 'c -> 'c) -> 'a t -> 'c -> 'c (** [fold f t x] fold [f] over every nodes of [t], with [x] as initial value. *) -val map : ('b -> 'c option) -> ('a,'b) t -> ('a,'c) t +val map : ('a -> 'b option) -> 'a t -> 'b t (** [map f t] maps [f] over every values stored in [t]. The return value of [f] is of type 'c option as one may wants to remove value associated to a key. This function is not tail-recursive. *) -val sub : ('a, 'b) t -> 'a list -> ('a,'b) t +val sub : 'a t -> string list -> 'a t (** [sub t p] returns the sub-trie associated with the path [p] in the trie [t]. If [p] is not a valid path of [t], it returns an empty trie. *)