about summary refs log tree commit diff
diff options
context:
space:
mode:
authorkadmin <julianknodt@gmail.com>2020-11-18 22:59:47 +0000
committerkadmin <julianknodt@gmail.com>2020-11-22 22:22:03 +0000
commita9915581d7cb73e7c8fb8193f48dbef36a7d09ac (patch)
tree1ee97f767f67417a36f9d3789a3bb555ec9a7b8a
parenta1a13b2bc4fa6370b9501135d97c5fe0bc401894 (diff)
downloadrust-a9915581d7cb73e7c8fb8193f48dbef36a7d09ac.tar.gz
rust-a9915581d7cb73e7c8fb8193f48dbef36a7d09ac.zip
Change slice::to_vec to not use extend_from_slice
This also required adding a loop guard in case clone panics

Add specialization for copy

There is a better version for copy, so I've added specialization for that function
and hopefully that should speed it up even more.

Switch FromIter<slice::Iter> to use `to_vec`

Test different unrolling version for to_vec

Revert to impl

From benchmarking, it appears this version is faster
-rw-r--r--library/alloc/src/slice.rs66
-rw-r--r--library/alloc/src/vec.rs26
-rw-r--r--src/test/codegen/to_vec.rs10
3 files changed, 85 insertions, 17 deletions
diff --git a/library/alloc/src/slice.rs b/library/alloc/src/slice.rs
index 41ebb1cf654..949a3bb1d70 100644
--- a/library/alloc/src/slice.rs
+++ b/library/alloc/src/slice.rs
@@ -155,13 +155,65 @@ mod hack {
     }
 
     #[inline]
-    pub fn to_vec<T, A: AllocRef>(s: &[T], alloc: A) -> Vec<T, A>
-    where
-        T: Clone,
-    {
-        let mut vec = Vec::with_capacity_in(s.len(), alloc);
-        vec.extend_from_slice(s);
-        vec
+    pub fn to_vec<T: ConvertVec, A: AllocRef>(s: &[T], alloc: A) -> Vec<T, A> {
+        T::to_vec(s, alloc)
+    }
+
+    pub trait ConvertVec {
+        fn to_vec<A: AllocRef>(s: &[Self], alloc: A) -> Vec<Self, A>
+        where
+            Self: Sized;
+    }
+
+    impl<T: Clone> ConvertVec for T {
+        #[inline]
+        default fn to_vec<A: AllocRef>(s: &[Self], alloc: A) -> Vec<Self, A> {
+            struct DropGuard<'a, T, A: AllocRef> {
+                vec: &'a mut Vec<T, A>,
+                num_init: usize,
+            }
+            impl<'a, T, A: AllocRef> Drop for DropGuard<'a, T, A> {
+                #[inline]
+                fn drop(&mut self) {
+                    // SAFETY:
+                    // items were marked initialized in the loop below
+                    unsafe {
+                        self.vec.set_len(self.num_init);
+                    }
+                }
+            }
+            let mut vec = Vec::with_capacity_in(s.len(), alloc);
+            let mut guard = DropGuard { vec: &mut vec, num_init: 0 };
+            let slots = guard.vec.spare_capacity_mut();
+            // .take(slots.len()) is necessary for LLVM to remove bounds checks
+            // and has better codegen than zip.
+            for (i, b) in s.iter().enumerate().take(slots.len()) {
+                guard.num_init = i;
+                slots[i].write(b.clone());
+            }
+            core::mem::forget(guard);
+            // SAFETY:
+            // the vec was allocated and initialized above to at least this length.
+            unsafe {
+                vec.set_len(s.len());
+            }
+            vec
+        }
+    }
+
+    impl<T: Copy> ConvertVec for T {
+        #[inline]
+        fn to_vec<A: AllocRef>(s: &[Self], alloc: A) -> Vec<Self, A> {
+            let mut v = Vec::with_capacity_in(s.len(), alloc);
+            // SAFETY:
+            // allocated above with the capacity of `s`, and initialize to `s.len()` in
+            // ptr::copy_to_non_overlapping below.
+            unsafe {
+                s.as_ptr().copy_to_nonoverlapping(v.as_mut_ptr(), s.len());
+                v.set_len(s.len());
+            }
+            v
+        }
     }
 }
 
diff --git a/library/alloc/src/vec.rs b/library/alloc/src/vec.rs
index 392c16546ef..21ae7d6d8a2 100644
--- a/library/alloc/src/vec.rs
+++ b/library/alloc/src/vec.rs
@@ -2508,17 +2508,23 @@ where
     }
 }
 
-impl<'a, T: 'a> SpecFromIter<&'a T, slice::Iter<'a, T>> for Vec<T>
-where
-    T: Copy,
-{
-    // reuses the extend specialization for T: Copy
+// This utilizes `iterator.as_slice().to_vec()` since spec_extend
+// must take more steps to reason about the final capacity + length
+// and thus do more work. `to_vec()` directly allocates the correct amount
+// and fills it exactly.
+impl<'a, T: 'a + Clone> SpecFromIter<&'a T, slice::Iter<'a, T>> for Vec<T> {
+    #[cfg(not(test))]
     fn from_iter(iterator: slice::Iter<'a, T>) -> Self {
-        let mut vec = Vec::new();
-        // must delegate to spec_extend() since extend() itself delegates
-        // to spec_from for empty Vecs
-        vec.spec_extend(iterator);
-        vec
+        iterator.as_slice().to_vec()
+    }
+
+    // HACK(japaric): with cfg(test) the inherent `[T]::to_vec` method, which is
+    // required for this method definition, is not available. Instead use the
+    // `slice::to_vec`  function which is only available with cfg(test)
+    // NB see the slice::hack module in slice.rs for more information
+    #[cfg(test)]
+    fn from_iter(iterator: slice::Iter<'a, T>) -> Self {
+        crate::slice::to_vec(iterator.as_slice(), Global)
     }
 }
 
diff --git a/src/test/codegen/to_vec.rs b/src/test/codegen/to_vec.rs
new file mode 100644
index 00000000000..60dc4efcb62
--- /dev/null
+++ b/src/test/codegen/to_vec.rs
@@ -0,0 +1,10 @@
+// compile-flags: -O
+
+#![crate_type = "lib"]
+
+// CHECK-LABEL: @copy_to_vec
+#[no_mangle]
+fn copy_to_vec(s: &[u64]) -> Vec<u64> {
+  s.to_vec()
+  // CHECK: call void @llvm.memcpy
+}