gstvalue: Optimize some list<=>list functions
authorEdward Hervey <edward@centricular.com>
Wed, 18 Mar 2020 08:43:27 +0000 (09:43 +0100)
committerEdward Hervey <bilboed@bilboed.com>
Wed, 18 Mar 2020 16:53:34 +0000 (17:53 +0100)
For subtracting a list from another, the previous implementation would
do a double subtraction of one from another (which would create temporary
arrays/values which would then be discarded). Instead iterate and do
the comparision directly.

For intersecting a list with another, we can directly iterate both at
once and therefore avoid doing a *full* check of all values of the list
against all other values of the list.

gst/gstvalue.c

index 8df2d803995666f67a3b2c9f7d750ece80779c72..c3fb6c17ee420ce8ed99580852f66f795ea464cc 100644 (file)
@@ -4258,6 +4258,39 @@ gst_value_is_subset_structure_structure (const GValue * value1,
   return gst_structure_is_subset (s1, s2);
 }
 
+static gboolean
+gst_value_is_subset_list_list (const GValue * value1, const GValue * value2)
+{
+  GstValueList *vlist1 = VALUE_LIST_ARRAY (value1);
+  GstValueList *vlist2 = VALUE_LIST_ARRAY (value2);
+  gint it1, len1, it2, len2;
+
+  len1 = vlist1->len;
+  len2 = vlist2->len;
+
+  /* A list can't be a subset of a smaller list */
+  if (len1 > len2)
+    return FALSE;
+
+  /* Check if all elements of the first list are within the 2nd list */
+  for (it1 = 0; it1 < len1; it1++) {
+    const GValue *child1 = &vlist1->fields[it1];
+    gboolean seen = FALSE;
+
+    for (it2 = 0; it2 < len2; it2++) {
+      const GValue *child2 = &vlist2->fields[it2];
+      if (gst_value_compare (child1, child2) == GST_VALUE_EQUAL) {
+        seen = TRUE;
+        break;
+      }
+    }
+    if (!seen)
+      return FALSE;
+  }
+
+  return TRUE;
+}
+
 /**
  * gst_value_is_subset:
  * @value1: a #GValue
@@ -4285,6 +4318,8 @@ gst_value_is_subset (const GValue * value1, const GValue * value2)
   } else if (GST_VALUE_HOLDS_STRUCTURE (value1)
       && GST_VALUE_HOLDS_STRUCTURE (value2)) {
     return gst_value_is_subset_structure_structure (value1, value2);
+  } else if (GST_VALUE_HOLDS_LIST (value1) && GST_VALUE_HOLDS_LIST (value2)) {
+    return gst_value_is_subset_list_list (value1, value2);
   }
 
   /*
@@ -4722,6 +4757,114 @@ gst_value_intersect_double_range_double_range (GValue * dest,
   return FALSE;
 }
 
+static gboolean
+gst_value_intersect_list_list (GValue * dest, const GValue * value1,
+    const GValue * value2)
+{
+  guint8 tmpfield[16];          /* Check up to 128 values */
+  guint8 *bitfield;
+  gboolean alloc_bitfield = FALSE;
+  gboolean res = FALSE;
+  GValue *tmp;
+  GType type1, type2;
+  guint it1, len1, it2, len2, itar;
+  GstValueList *vlist = NULL;
+
+  /* If they don't have the same basic type, all bets are off :) */
+  if (!gst_value_list_or_array_get_basic_type (value1, &type1) ||
+      !gst_value_list_or_array_get_basic_type (value2, &type2) ||
+      type1 != type2)
+    return FALSE;
+
+  len1 = VALUE_LIST_SIZE (value1);
+  len2 = VALUE_LIST_SIZE (value2);
+
+  /* Fast-path with no dest (i.e. only interested in knowing whether
+   * both lists intersected without wanting the result) */
+  if (!dest) {
+    for (it1 = 0; it1 < len1; it1++) {
+      const GValue *item1 = VALUE_LIST_GET_VALUE (value1, it1);
+      for (it2 = 0; it2 < len2; it2++) {
+        const GValue *item2 = VALUE_LIST_GET_VALUE (value2, it2);
+        if (gst_value_intersect (NULL, item1, item2)) {
+          res = TRUE;
+          goto beach;
+        }
+      }
+    }
+    goto beach;
+  }
+#define is_visited(idx) (bitfield[(idx) >> 3]    & (1 << ((idx) & 0x7)))
+#define mark_visited(idx) (bitfield[(idx) >> 3] |= (1 << ((idx) & 0x7)))
+
+  /* Bitfield to avoid double-visiting */
+  if (G_UNLIKELY (len2 > 128)) {
+    alloc_bitfield = TRUE;
+    bitfield = g_malloc0 ((len2 / 8) + 1);
+    GST_CAT_LOG (GST_CAT_PERFORMANCE,
+        "Allocation for GstValueList with more than 128 members");
+  } else {
+    bitfield = &tmpfield[0];
+    memset (bitfield, 0, 16);
+  }
+
+
+  /* When doing list<->list intersections, there is a greater
+   * probability of ending up with a list than with a single value.
+   * Furthermore, the biggest that list can be will the smallest list
+   * (i.e. intersects fully).
+   * Therefore we pre-allocate a GstValueList of that size. */
+  vlist = _gst_value_list_new (MIN (len1, len2));
+
+  itar = 0;
+  tmp = &vlist->fields[0];
+
+  for (it1 = 0; it1 < len1; it1++) {
+    const GValue *item1 = VALUE_LIST_GET_VALUE (value1, it1);
+    for (it2 = 0; it2 < len2; it2++) {
+      const GValue *item2;
+      if (is_visited (it2))
+        continue;
+      item2 = VALUE_LIST_GET_VALUE (value2, it2);
+
+      if (gst_value_intersect (tmp, item1, item2)) {
+        res = TRUE;
+        mark_visited (it2);
+
+        /* Move our collection value */
+        itar += 1;
+        tmp = &vlist->fields[itar];
+        vlist->len += 1;
+
+        /* We can stop iterating the second part now that we've matched */
+        break;
+      }
+    }
+  }
+
+#undef is_visited
+#undef mark_visited
+
+  if (res) {
+    /* If we end up with a single value in the list, just use that
+     * value. Else use the list */
+    if (vlist->len == 1) {
+      gst_value_move (dest, &vlist->fields[0]);
+      g_free (vlist);
+    } else {
+      dest->g_type = GST_TYPE_LIST;
+      dest->data[0].v_pointer = vlist;
+    }
+  } else {
+    g_free (vlist);
+  }
+
+beach:
+  if (alloc_bitfield)
+    g_free (bitfield);
+  return res;
+}
+
 static gboolean
 gst_value_intersect_list (GValue * dest, const GValue * value1,
     const GValue * value2)
@@ -4730,6 +4873,11 @@ gst_value_intersect_list (GValue * dest, const GValue * value1,
   GValue intersection = { 0, };
   gboolean ret = FALSE;
 
+  /* Use optimized list-list intersection */
+  if (G_VALUE_TYPE (value2) == GST_TYPE_LIST) {
+    return gst_value_intersect_list_list (dest, value1, value2);
+  }
+
   size = VALUE_LIST_SIZE (value1);
   for (i = 0; i < size; i++) {
     const GValue *cur = VALUE_LIST_GET_VALUE (value1, i);