Performance improvement for most scenes
diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py
index b6bb4be4b9..94c111eb23 100644
--- a/manimlib/camera/camera.py
+++ b/manimlib/camera/camera.py
@@ -338,15 +338,15 @@ def set_cairo_context_path(self, ctx, vmobject):
             return
 
         ctx.new_path()
-        subpaths = vmobject.get_subpaths_from_points(points)
+        subpaths = vmobject.gen_subpaths_from_points_2d(points)
         for subpath in subpaths:
-            quads = vmobject.get_cubic_bezier_tuples_from_points(subpath)
+            quads = vmobject.gen_cubic_bezier_tuples_from_points(subpath)
             ctx.new_sub_path()
             start = subpath[0]
             ctx.move_to(*start[:2])
             for p0, p1, p2, p3 in quads:
                 ctx.curve_to(*p1[:2], *p2[:2], *p3[:2])
-            if vmobject.consider_points_equals(subpath[0], subpath[-1]):
+            if vmobject.consider_points_equals_2d(subpath[0], subpath[-1]):
                 ctx.close_path()
         return self
 
@@ -549,7 +549,7 @@ def adjust_out_of_range_points(self, points):
     def transform_points_pre_display(self, mobject, points):
         # Subclasses (like ThreeDCamera) may want to
         # adjust points futher before they're shown
-        if np.any(np.isnan(points)) or np.any(points == np.inf):
+        if not np.all(np.isfinite(points)):
             # TODO, print some kind of warning about
             # mobject having invalid points?
             points = np.zeros((1, 3))
diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py
index 29b91e544d..25ba527dad 100644
--- a/manimlib/mobject/types/vectorized_mobject.py
+++ b/manimlib/mobject/types/vectorized_mobject.py
@@ -595,35 +595,69 @@ def consider_points_equals(self, p0, p1):
             atol=self.tolerance_for_point_equality
         )
 
+    def consider_points_equals_2d(self, p0, p1):
+        """
+        Determine if two points are close enough to be considered equal.
+
+        This uses the algorithm from np.isclose(), but expanded here for the
+        2D point case. NumPy is overkill for such a small question.
+        """
+        rtol = 1.e-5  # default from np.isclose()
+        atol = self.tolerance_for_point_equality
+        if abs(p0[0] - p1[0]) > atol + rtol * abs(p1[0]):
+            return False
+        if abs(p0[1] - p1[1]) > atol + rtol * abs(p1[1]):
+            return False
+        return True
+
     # Information about line
     def get_cubic_bezier_tuples_from_points(self, points):
+        return np.array(list(self.gen_cubic_bezier_tuples_from_points(points)))
+
+    def gen_cubic_bezier_tuples_from_points(self, points):
+        """
+        Get a generator for the cubic bezier tuples of this object.
+
+        Generator to not materialize a list or np.array needlessly.
+        """
         nppcc = VMobject.CONFIG["n_points_per_cubic_curve"]
         remainder = len(points) % nppcc
         points = points[:len(points) - remainder]
-        return np.array([
+        return (
             points[i:i + nppcc]
             for i in range(0, len(points), nppcc)
-        ])
+        )
 
     def get_cubic_bezier_tuples(self):
         return self.get_cubic_bezier_tuples_from_points(
             self.get_points()
         )
 
-    def get_subpaths_from_points(self, points):
+    def _gen_subpaths_from_points(self, points, filter_func):
         nppcc = self.n_points_per_cubic_curve
-        split_indices = filter(
-            lambda n: not self.consider_points_equals(
-                points[n - 1], points[n]
-            ),
-            range(nppcc, len(points), nppcc)
-        )
+        split_indices = filter(filter_func, range(nppcc, len(points), nppcc))
         split_indices = [0] + list(split_indices) + [len(points)]
-        return [
+        return (
             points[i1:i2]
             for i1, i2 in zip(split_indices, split_indices[1:])
             if (i2 - i1) >= nppcc
-        ]
+        )
+
+    def get_subpaths_from_points(self, points):
+        return list(
+            self._gen_subpaths_from_points(
+                points,
+                lambda n: not self.consider_points_equals(
+                    points[n - 1], points[n]
+                ))
+        )
+
+    def gen_subpaths_from_points_2d(self, points):
+        return self._gen_subpaths_from_points(
+                points,
+                lambda n: not self.consider_points_equals_2d(
+                    points[n - 1], points[n]
+                ))
 
     def get_subpaths(self):
         return self.get_subpaths_from_points(self.get_points())
diff --git a/perf_scenes.py b/perf_scenes.py
new file mode 100644
index 0000000000..80d468e7b0
--- /dev/null
+++ b/perf_scenes.py
@@ -0,0 +1,88 @@
+from manimlib.imports import *
+
+"""
+A set of scenes to be used for performance testing of Manim.
+"""
+
+
+class Perf1(GraphScene):
+    """
+    A simple scene of two animations from the end of a video on recursion.
+
+    - Uses a graph in 1/4 of the scene.
+    - First fades in multiple lines of text and equations, and the graph axes.
+    - Next animates creation of two graphs and the creation of their text
+      labels.
+    """
+    CONFIG = {
+        "x_axis_label":
+        "$n$",
+        "y_axis_label":
+        "$time$",
+        "x_axis_width":
+        FRAME_HEIGHT,
+        "y_axis_height":
+        FRAME_HEIGHT / 2,
+        "y_max":
+        50,
+        "y_min":
+        0,
+        "x_max":
+        100,
+        "x_min":
+        0,
+        "x_labeled_nums": [50, 100],
+        "y_labeled_nums":
+        range(0, 51, 10),
+        "y_tick_frequency":
+        10,
+        "x_tick_frequency":
+        10,
+        "axes_color":
+        BLUE,
+        "graph_origin":
+        np.array(
+            (-FRAME_X_RADIUS + LARGE_BUFF, -FRAME_Y_RADIUS + LARGE_BUFF, 0))
+    }
+
+    def construct(self):
+        t1 = TextMobject(
+            "Dividing a problem in half over and over means\\\\"
+            "the work done is proportional to $\\log_2{n}$").to_edge(UP)
+
+        t2 = TextMobject(
+            '\\textit{This is one of our\\\\favorite things to do in CS!}')
+        t2.to_edge(RIGHT)
+
+        t3 = TextMobject(
+            'The new \\texttt{power(x,n)} is \\underline{much}\\\\better than the old!'
+        )
+        t3.scale(0.8)
+        p1f = TexMobject('x^n=x \\times x^{n-1}').set_color(ORANGE)
+        t4 = TextMobject('\\textit{vs.}').scale(0.8)
+        p2f = TexMobject(
+            'x^n=x^{\\frac{n}{2}} \\times x^{\\frac{n}{2}}').set_color(GREEN)
+        p1v2g = VGroup(t3, p1f, t4, p2f).arrange(DOWN).center().to_edge(RIGHT)
+
+        self.setup_axes()
+        o_n = self.get_graph(lambda x: x, color=ORANGE, x_min=1, x_max=50)
+        o_log2n = self.get_graph(lambda x: math.log2(x),
+                                 color=GREEN,
+                                 x_min=2,
+                                 x_max=90)
+        onl = TexMobject('O(n)')
+        olog2nl = TexMobject('O(\\log_2{n})')
+        onl.next_to(o_n.get_point_from_function(0.6), UL)
+        olog2nl.next_to(o_log2n.get_point_from_function(0.8), UP)
+        self.play(
+            FadeIn(t1),
+            FadeIn(self.axes),
+            # FadeInFromDown(t2),
+            FadeIn(p1v2g),
+        )
+        self.play(ShowCreation(o_n),
+                  ShowCreation(o_log2n),
+                  ShowCreation(onl),
+                  ShowCreation(olog2nl),
+                  run_time=3)
+        self.wait(duration=5)

tl;dr: this is a significant performance improvement for many scenes. 1.7x - 2.6x improvement in animation it/s.

This is a small change to some of the hotest paths in rendering objects. The biggest win comes from not using np.allclose() to check if two points are close enough. In general, NumPy is awesome at operating on large arrays, but overkill for very tiny questions like this. Created a small function to determine if two points are close using the same algorithm, and limited it to 2D points since that's all we need in set_cairo_context_path().

A couple of other minor tweaks to reduce or eliminate other uses of NumPy in this path.

In general, it is better to avoid wrapping lists in np.array when a real NumPy array isn't actually needed.

Added a new file for performance test scenes, with a single scene from the end of a video I've been working on.

Data:

MacBook Pro (16-inch, 2019)
macOS Catalina 10.15.4
2.4 GHz 8-Core Intel Core i9
64 GB 2667 MHz DDR4
Python 3.7.3 (default, Mar  6 2020, 22:34:30)

Profiler: yappi under Pycharm.

Using the scene Perf1 from the included perf_scenes.py, averaged over 5 runs and rendered with:
manim.py perf_scenes.py Perf1 -pl --leave_progress_bars

Before:
Animation 0: FadeInTextMobject, etc.:               8.93it/s
Animation 1: ShowCreationParametricFunction, etc.: 84.66it/s

Profiler shows 48.5% of the run spent under Camera.set_cairo_context_path()

After
Animation 0: FadeInTextMobject, etc.:               23.45it/s  -- 2.63x improvement
Animation 1: ShowCreationParametricFunction, etc.: 149.62it/s  -- 1.77x improvement

Profiler shows 19.9% of the run spent under Camera.set_cairo_context_path()

Less improvement with production-quality renders, and percent improvement varies with scene of course. This appears to be a good win for every scene I'm working on though. I hope it will be for others, too.

NB: there are more perf improvements to be had, of course, but this is the best one I currently have.

Thanks for contributing to manim!

**Please ensure that your pull request works with the latest version of manim.**
You should also include:

1. The motivation for making this change (or link the relevant issues)
2. How you tested the new behavior (e.g. a minimal working example, before/after
screenshots, gifs, commands, etc.) This is rather informal at the moment, but
the goal is to show us how you know the pull request works as intended.
