Composed, for lightweight operator composition
authorTom Jackson <tjackson@fb.com>
Thu, 1 Nov 2012 00:58:37 +0000 (17:58 -0700)
committerJordan DeLong <jdelong@fb.com>
Sun, 16 Dec 2012 22:43:34 +0000 (14:43 -0800)
Summary:
Sometimes it'll be handy to have a custom operator which is little more than the
composition of a few base operators. This makes that really easy to do, as shown
in examples, tests, and benchmarks.

Test Plan: Unit tests, benchmarks

Reviewed By: rsagula@fb.com

FB internal diff: D617152

folly/experimental/Gen-inl.h
folly/experimental/Gen.h
folly/experimental/test/GenBenchmark.cpp
folly/experimental/test/GenTest.cpp

index c213f138c4b00fb8e60c257a18951e6941b88883..037c330caf4522c7103db0fd083244015529e483 100644 (file)
@@ -92,6 +92,16 @@ class Operator : public FBounded<Self> {
            class Value,
            class ResultGen = void>
   ResultGen compose(const GenImpl<Value, Source>& source) const;
+
+  /**
+   * operator|() - For composing two operators without binding it to a
+   * particular generator.
+   */
+  template<class Next,
+           class Composed = detail::Composed<Self, Next>>
+  Composed operator|(const Operator<Next>& op) const {
+    return Composed(this->self(), op.self());
+  }
  protected:
   Operator() = default;
   Operator(const Operator&) = default;
@@ -779,6 +789,39 @@ class Order : public Operator<Order<Selector, Comparer>> {
   }
 };
 
+/**
+ * Composed - For building up a pipeline of operations to perform, absent any
+ * particular source generator. Useful for building up custom pipelines.
+ *
+ * This type is usually used by just piping two operators together:
+ *
+ * auto valuesOf = filter([](Optional<int>& o) { return o.hasValue(); })
+ *               | map([](Optional<int>& o) -> int& { return o.value(); });
+ *
+ *  auto valuesIncluded = from(optionals) | valuesOf | as<vector>();
+ */
+template<class First,
+         class Second>
+class Composed : public Operator<Composed<First, Second>> {
+  const First first_;
+  const Second second_;
+  public:
+    Composed() {}
+    Composed(const First& first, const Second& second)
+      : first_(first)
+      , second_(second) {}
+
+  template<class Source,
+           class Value,
+           class FirstRet = decltype(std::declval<First>()
+                                     .compose(std::declval<Source>())),
+           class SecondRet = decltype(std::declval<Second>()
+                                      .compose(std::declval<FirstRet>()))>
+  SecondRet compose(const GenImpl<Value, Source>& source) const {
+    return second_.compose(first_.compose(source.self()));
+  }
+};
+
 /*
  * Sinks
  */
index 0797637db92e89374ba0f65c199552ce59b750fb..9126d59b0241f0ae4dc19818595fef0a276d1807 100644 (file)
@@ -194,6 +194,8 @@ class Skip;
 template<class Selector, class Comparer = Less>
 class Order;
 
+template<class First, class Second>
+class Composed;
 
 /*
  * Sinks
index ad9bf7f18941b30798512f720eb1bdbc3cd52e6f..18435c26ced035a88b2eca7c9f401e98ddde901e 100644 (file)
@@ -128,7 +128,6 @@ BENCHMARK(Fib_Sum_NoGen, iters) {
     };
     for (auto& v : fib(testSize.load())) {
       s += v;
-      v = s;
     }
   }
   folly::doNotOptimizeAway(s);
@@ -166,7 +165,7 @@ BENCHMARK_RELATIVE(Fib_Sum_Gen_Static, iters) {
   int s = 0;
   while (iters--) {
     auto fib = generator<int>(FibYielder());
-    s += fib | take(30) | sum;
+    s += fib | take(testSize.load()) | sum;
   }
   folly::doNotOptimizeAway(s);
 }
@@ -239,31 +238,64 @@ BENCHMARK_RELATIVE(Concat_Gen, iters) {
   folly::doNotOptimizeAway(s);
 }
 
+BENCHMARK_DRAW_LINE()
+
+BENCHMARK(Composed_NoGen, iters) {
+  int s = 0;
+  while (iters--) {
+    for (auto& i : testVector) {
+      s += i * i;
+    }
+  }
+  folly::doNotOptimizeAway(s);
+}
+
+BENCHMARK_RELATIVE(Composed_Gen, iters) {
+  int s = 0;
+  auto sumSq = map(square) | sum;
+  while (iters--) {
+    s += from(testVector) | sumSq;
+  }
+  folly::doNotOptimizeAway(s);
+}
+
+BENCHMARK_RELATIVE(Composed_GenRegular, iters) {
+  int s = 0;
+  while (iters--) {
+    s += from(testVector) | map(square) | sum;
+  }
+  folly::doNotOptimizeAway(s);
+}
+
 // Results from a dual core Xeon L5520 @ 2.27GHz:
 //
 // ============================================================================
 // folly/experimental/test/GenBenchmark.cpp        relative  time/iter  iters/s
 // ============================================================================
 // Sum_Basic_NoGen                                            301.60ns    3.32M
-// Sum_Basic_Gen                                    103.20%   292.24ns    3.42M
+// Sum_Basic_Gen                                    104.27%   289.24ns    3.46M
 // ----------------------------------------------------------------------------
 // Sum_Vector_NoGen                                           200.33ns    4.99M
-// Sum_Vector_Gen                                    99.44%   201.45ns    4.96M
+// Sum_Vector_Gen                                    99.81%   200.70ns    4.98M
+// ----------------------------------------------------------------------------
+// Count_Vector_NoGen                                          12.37us   80.84K
+// Count_Vector_Gen                                 103.09%    12.00us   83.33K
 // ----------------------------------------------------------------------------
-// Count_Vector_NoGen                                          19.07fs   52.43T
-// Count_Vector_Gen                                 166.67%    11.44fs   87.38T
+// Fib_Sum_NoGen                                                3.66us  273.21K
+// Fib_Sum_Gen                                       43.06%     8.50us  117.65K
+// Fib_Sum_Gen_Static                                87.81%     4.17us  239.89K
 // ----------------------------------------------------------------------------
-// Fib_Sum_NoGen                                                4.15us  241.21K
-// Fib_Sum_Gen                                       48.75%     8.50us  117.58K
-// Fib_Sum_Gen_Static                               113.24%     3.66us  273.16K
+// VirtualGen_0Virtual                                         10.04us   99.61K
+// VirtualGen_1Virtual                               29.59%    33.93us   29.47K
+// VirtualGen_2Virtual                               20.45%    49.10us   20.37K
+// VirtualGen_3Virtual                               15.49%    64.82us   15.43K
 // ----------------------------------------------------------------------------
-// VirtualGen_0Virtual                                         10.05us   99.48K
-// VirtualGen_1Virtual                               29.63%    33.93us   29.47K
-// VirtualGen_2Virtual                               20.47%    49.09us   20.37K
-// VirtualGen_3Virtual                               15.30%    65.68us   15.23K
+// Concat_NoGen                                                 2.50us  400.37K
+// Concat_Gen                                       102.50%     2.44us  410.37K
 // ----------------------------------------------------------------------------
-// Concat_NoGen                                                 2.34us  427.15K
-// Concat_Gen                                        90.04%     2.60us  384.59K
+// Composed_NoGen                                             549.54ns    1.82M
+// Composed_Gen                                     101.39%   542.00ns    1.85M
+// Composed_GenRegular                               99.66%   551.40ns    1.81M
 // ============================================================================
 
 int main(int argc, char *argv[]) {
index bb85abab86c7b99fb2f43d1ea2e62cb212776f1c..06499877887bf76da7ced16664d70017afb2a33a 100644 (file)
@@ -166,6 +166,19 @@ TEST(Gen, Until) {
   EXPECT_EQ(31, gen | count);
 }
 
+TEST(Gen, Composed) {
+  // Operator, Operator
+  auto valuesOf =
+      filter([](Optional<int>& o) { return o.hasValue(); })
+    | map([](Optional<int>& o) -> int& { return o.value(); });
+  std::vector<Optional<int>> opts {
+    none, 4, none, 6, none
+  };
+  EXPECT_EQ(4 * 4 + 6 * 6, from(opts) | valuesOf | map(square) | sum);
+  // Operator, Sink
+  auto sumOpt = valuesOf | sum;
+  EXPECT_EQ(10, from(opts) | sumOpt);
+}
 
 TEST(Gen, Chain) {
   std::vector<int> nums {2, 3, 5, 7};