This patch improves the efficiency of Ada.Containers.Unbounded_Priority_Queues,
especially in the case where many same-priority items are enqueued.

No test, because no change in behavior.

Tested on x86_64-pc-linux-gnu, committed on trunk

2016-04-18  Bob Duff  <d...@adacore.com>

        * a-cuprqu.ads: Change the representation of List_Type from a
        singly-linked list to a doubly-linked list. In addition, add a
        pointer Next_Unequal, which points past a possibly-long chain
        of equal-priority items. This increases efficiency, especially
        in the case of many equal-priority items.
        * a-cuprqu.adb (Dequeue, Enqueue): Rewrite algorithms to take
        advantage of new data structure.
        (Finalize): Rewrite in terms of Dequeue, for simplicity.

Index: a-cuprqu.adb
===================================================================
--- a-cuprqu.adb        (revision 235093)
+++ a-cuprqu.adb        (working copy)
@@ -37,9 +37,22 @@
       -- Local Subprograms --
       -----------------------
 
+      function Before_Or_Equal (X, Y : Queue_Priority) return Boolean;
+      --  True if X is before or equal to Y. Equal means both Before(X,Y) and
+      --  Before(Y,X) are False.
+
       procedure Free is
-         new Ada.Unchecked_Deallocation (Node_Type, Node_Access);
+        new Ada.Unchecked_Deallocation (Node_Type, Node_Access);
 
+      ---------------------
+      -- Before_Or_Equal --
+      ---------------------
+
+      function Before_Or_Equal (X, Y : Queue_Priority) return Boolean is
+      begin
+         return (if Before (X, Y) then True else not Before (Y, X));
+      end Before_Or_Equal;
+
       -------------
       -- Dequeue --
       -------------
@@ -48,20 +61,36 @@
         (List    : in out List_Type;
          Element : out Queue_Interfaces.Element_Type)
       is
-         X : Node_Access;
+         H : constant Node_Access := List.Header'Unchecked_Access;
+         pragma Assert (List.Length /= 0);
+         pragma Assert (List.Header.Next /= H);
+         --  List can't be empty; see the barrier
 
-      begin
-         Element := List.First.Element;
+         pragma Assert
+           (List.Header.Next.Next = H or else
+            Before_Or_Equal (Get_Priority (List.Header.Next.Element),
+                             Get_Priority (List.Header.Next.Next.Element)));
+         --  The first item is before-or-equal to the second
 
-         X := List.First;
-         List.First := List.First.Next;
+         pragma Assert
+           (List.Header.Next.Next_Unequal = H or else
+            Before (Get_Priority (List.Header.Next.Element),
+                    Get_Priority (List.Header.Next.Next_Unequal.Element)));
+         --  The first item is before its Next_Unequal item
 
-         if List.First = null then
-            List.Last := null;
-         end if;
+         --  The highest-priority item is always first; just remove it and
+         --  return that element.
 
+         X : Node_Access := List.Header.Next;
+
+      --  Start of processing for Dequeue
+
+      begin
+         Element := X.Element;
+         X.Next.Prev := H;
+         List.Header.Next := X.Next;
+         List.Header.Next_Unequal := X.Next;
          List.Length := List.Length - 1;
-
          Free (X);
       end Dequeue;
 
@@ -93,15 +122,13 @@
          --  dequeue an item. If it's false, it means no item is dequeued, and
          --  we return False as the Success value.
 
-         if List.Length = 0
-           or else Before (At_Least, Get_Priority (List.First.Element))
-         then
-            Success := False;
-            return;
+         Success := List.Length > 0
+           and then
+             not Before (At_Least, Get_Priority (List.Header.Next.Element));
+
+         if Success then
+            List.Dequeue (Element);
          end if;
-
-         List.Dequeue (Element);
-         Success := True;
       end Dequeue;
 
       -------------
@@ -113,41 +140,55 @@
          New_Item : Queue_Interfaces.Element_Type)
       is
          P : constant Queue_Priority := Get_Priority (New_Item);
+         H : constant Node_Access := List.Header'Unchecked_Access;
 
-         Node : Node_Access;
-         Prev : Node_Access;
+         function Next return Node_Access;
+         --  The node before which we wish to insert the new node
 
-      begin
-         Node := new Node_Type'(New_Item, null);
+         ----------
+         -- Next --
+         ----------
 
-         if List.First = null then
-            List.First := Node;
-            List.Last := List.First;
+         function Next return Node_Access is
+         begin
+            return Result : Node_Access := H.Next_Unequal do
+               while Result /= H
+                 and then not Before (P, Get_Priority (Result.Element))
+               loop
+                  Result := Result.Next_Unequal;
+               end loop;
+            end return;
+         end Next;
 
-         else
-            Prev := List.First;
+         --  Local varaibles
 
-            if Before (P, Get_Priority (Prev.Element)) then
-               Node.Next := List.First;
-               List.First := Node;
+         Prev : constant Node_Access := Next.Prev;
+         --  The node after which we wish to insert the new node. So Prev must
+         --  be the header, or be higher or equal priority to the new item.
+         --  Prev.Next must be the header, or be lower priority than the
+         --  new item.
 
-            else
-               while Prev.Next /= null loop
-                  if Before (P, Get_Priority (Prev.Next.Element)) then
-                     Node.Next := Prev.Next;
-                     Prev.Next := Node;
+         pragma Assert
+           (Prev = H or else Before_Or_Equal (Get_Priority (Prev.Element), P));
+         pragma Assert
+           (Prev.Next = H
+              or else Before (P, Get_Priority (Prev.Next.Element)));
+         pragma Assert (Prev.Next = Prev.Next_Unequal);
 
-                     exit;
-                  end if;
+         Node : constant Node_Access :=
+                  new Node_Type'(New_Item,
+                                 Prev         => Prev,
+                                 Next         => Prev.Next,
+                                 Next_Unequal => Prev.Next);
 
-                  Prev := Prev.Next;
-               end loop;
+      --  Start of processing for Enqueue
 
-               if Prev.Next = null then
-                  List.Last.Next := Node;
-                  List.Last := Node;
-               end if;
-            end if;
+      begin
+         Prev.Next.Prev := Node;
+         Prev.Next := Node;
+
+         if List.Length = 0 then
+            List.Header.Next_Unequal := Node;
          end if;
 
          List.Length := List.Length + 1;
@@ -162,12 +203,10 @@
       --------------
 
       procedure Finalize (List : in out List_Type) is
-         X : Node_Access;
+         Ignore : Queue_Interfaces.Element_Type;
       begin
-         while List.First /= null loop
-            X := List.First;
-            List.First := List.First.Next;
-            Free (X);
+         while List.Length > 0 loop
+            List.Dequeue (Ignore);
          end loop;
       end Finalize;
 
Index: a-cuprqu.ads
===================================================================
--- a-cuprqu.ads        (revision 235093)
+++ a-cuprqu.ads        (working copy)
@@ -81,18 +81,35 @@
 
    private
 
+      --  List_Type is implemented as a circular doubly-linked list with a
+      --  dummy header node; Prev and Next are the links. The list is in
+      --  decreasing priority order, so the highest-priority item is always
+      --  first. (If there are multiple items with the highest priority, the
+      --  oldest one is first.) Header.Element is undefined and not used.
+      --
+      --  In addition, Next_Unequal points to the next item with a different
+      --  (i.e. strictly lower) priority. This is used to speed up the search
+      --  for the next lower-priority item, in cases where there are many items
+      --  with the same priority.
+      --
+      --  An empty list has Header.Prev, Header.Next, and Header.Next_Unequal
+      --  all pointing to Header. A nonempty list has Header.Next_Unequal
+      --  pointing to the first "real" item, and the last item has Next_Unequal
+      --  pointing back to Header.
+
       type Node_Type;
-      type Node_Access is access Node_Type;
+      type Node_Access is access all Node_Type;
 
       type Node_Type is limited record
-         Element : Queue_Interfaces.Element_Type;
-         Next    : Node_Access;
+         Element      : Queue_Interfaces.Element_Type;
+         Prev, Next   : Node_Access := Node_Type'Unchecked_Access;
+         Next_Unequal : Node_Access := Node_Type'Unchecked_Access;
       end record;
 
       type List_Type is new Ada.Finalization.Limited_Controlled with record
-         First, Last : Node_Access;
-         Length      : Count_Type := 0;
-         Max_Length  : Count_Type := 0;
+         Header     : aliased Node_Type;
+         Length     : Count_Type := 0;
+         Max_Length : Count_Type := 0;
       end record;
 
       overriding procedure Finalize (List : in out List_Type);

Reply via email to