Repository: jorgecarleitao/arrow2
Pull request #305 (2021-08-20 14:06:15)

Improved performance of comparison with SIMD feature flag (2x-3.5x)
This PR adds explicit SIMD implementations to comparison compute.

By operating over groups of 8 and applying the relevant conversions with native packed_simd2 apis, we gain 2-3.5x in performance. There is no difference in performance over the non-simd implementation; it was just written in a more abstract fashion to allow the simd version to be plugged in.

```bash
cargo bench --no-default-features --features benchmarks,compute --bench comparison_kernels
cargo bench --no-default-features --features benchmarks,compute,simd --bench comparison_kernels
```

```bash
eq Float32              time:   [20.319 us 20.394 us 20.492 us]                        
                        change: [-70.496% -70.317% -70.165%] (p = 0.00 < 0.05)

eq scalar Float32       time:   [16.452 us 16.512 us 16.585 us]                               
                        change: [-54.292% -53.941% -53.602%] (p = 0.00 < 0.05)

lt Float32              time:   [19.904 us 19.972 us 20.063 us]                        
                        change: [-67.563% -67.329% -67.084%] (p = 0.00 < 0.05)

lt scalar Float32       time:   [16.410 us 16.467 us 16.541 us]                               
                        change: [-54.324% -54.088% -53.814%] (p = 0.00 < 0.05)
```

jorgecarleitao: There is an extra optimization in using the more natural md representation of the native types, but it requires a bit more work on the `MutableBitmap` to be safely constructed from `u8-u64` chunks
codecov[bot]: # [Codecov](https://codecov.io/gh/jorgecarleitao/arrow2/pull/305?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jorge+Leitao) Report
> Merging [#305](https://codecov.io/gh/jorgecarleitao/arrow2/pull/305?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jorge+Leitao) (9dc32a9) into [main](https://codecov.io/gh/jorgecarleitao/arrow2/commit/bbe607ccbf3d7079c1fac6ad53fc6e5d4368e165?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jorge+Leitao) (bbe607c) will **increase** coverage by `0.01%`.
> The diff coverage is `94.11%`.

[![Impacted file tree graph](https://codecov.io/gh/jorgecarleitao/arrow2/pull/305/graphs/tree.svg?width=650&height=150&src=pr&token=AgyTF60R3D&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jorge+Leitao)](https://codecov.io/gh/jorgecarleitao/arrow2/pull/305?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jorge+Leitao)

```diff
@@            Coverage Diff             @@
##             main     #305      +/-   ##
==========================================
+ Coverage   80.52%   80.53%   +0.01%     
==========================================
  Files         323      324       +1     
  Lines       21135    21156      +21     
==========================================
+ Hits        17019    17039      +20     
- Misses       4116     4117       +1     
```


| [Impacted Files](https://codecov.io/gh/jorgecarleitao/arrow2/pull/305?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jorge+Leitao) | Coverage Δ | |
|---|---|---|
| [src/compute/comparison/mod.rs](https://codecov.io/gh/jorgecarleitao/arrow2/pull/305/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jorge+Leitao#diff-c3JjL2NvbXB1dGUvY29tcGFyaXNvbi9tb2QucnM=) | `93.61% <ø> (ø)` | |
| [src/compute/nullif.rs](https://codecov.io/gh/jorgecarleitao/arrow2/pull/305/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jorge+Leitao#diff-c3JjL2NvbXB1dGUvbnVsbGlmLnJz) | `0.00% <0.00%> (ø)` | |
| [src/compute/comparison/simd/mod.rs](https://codecov.io/gh/jorgecarleitao/arrow2/pull/305/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jorge+Leitao#diff-c3JjL2NvbXB1dGUvY29tcGFyaXNvbi9zaW1kL21vZC5ycw==) | `95.83% <95.83%> (ø)` | |
| [src/compute/comparison/primitive.rs](https://codecov.io/gh/jorgecarleitao/arrow2/pull/305/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jorge+Leitao#diff-c3JjL2NvbXB1dGUvY29tcGFyaXNvbi9wcmltaXRpdmUucnM=) | `95.23% <100.00%> (-0.09%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/jorgecarleitao/arrow2/pull/305?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jorge+Leitao).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jorge+Leitao)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/jorgecarleitao/arrow2/pull/305?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jorge+Leitao). Last update [bbe607c...9dc32a9](https://codecov.io/gh/jorgecarleitao/arrow2/pull/305?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jorge+Leitao). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jorge+Leitao).


Patch:
diff --git a/benches/comparison_kernels.rs b/benches/comparison_kernels.rs
index a5b6a8293d..b1643bb1aa 100644
--- a/benches/comparison_kernels.rs
+++ b/benches/comparison_kernels.rs
@@ -32,7 +32,7 @@ where
 
 fn bench_op_scalar<T>(arr_a: &PrimitiveArray<T>, value_b: T, op: Operator)
 where
-    T: NativeType + std::cmp::PartialOrd,
+    T: NativeType + Simd8,
 {
     primitive_compare_scalar(
         criterion::black_box(arr_a),
diff --git a/src/array/fixed_size_binary/iterator.rs b/src/array/fixed_size_binary/iterator.rs
index b24117fea7..9eb77b43fd 100644
--- a/src/array/fixed_size_binary/iterator.rs
+++ b/src/array/fixed_size_binary/iterator.rs
@@ -1,46 +1,54 @@
-use crate::array::Array;
 use crate::bitmap::utils::{zip_validity, ZipValidity};
 
-use super::FixedSizeBinaryArray;
+use super::super::MutableArray;
+use super::{FixedSizeBinaryArray, FixedSizeBinaryValues, MutableFixedSizeBinaryArray};
 
 /// # Safety
 /// This iterator is `TrustedLen`
-pub struct FixedSizeBinaryValuesIter<'a> {
-    array: &'a FixedSizeBinaryArray,
+pub struct FixedSizeBinaryValuesIter<'a, T: FixedSizeBinaryValues> {
+    array: &'a T,
+    len: usize,
     index: usize,
 }
 
-impl<'a> FixedSizeBinaryValuesIter<'a> {
+impl<'a, T: FixedSizeBinaryValues> FixedSizeBinaryValuesIter<'a, T> {
     #[inline]
-    pub fn new(array: &'a FixedSizeBinaryArray) -> Self {
-        Self { array, index: 0 }
+    pub fn new(array: &'a T) -> Self {
+        Self {
+            array,
+            len: array.values().len() / array.size(),
+            index: 0,
+        }
     }
 }
 
-impl<'a> Iterator for FixedSizeBinaryValuesIter<'a> {
+impl<'a, T: FixedSizeBinaryValues> Iterator for FixedSizeBinaryValuesIter<'a, T> {
     type Item = &'a [u8];
 
     #[inline]
     fn next(&mut self) -> Option<Self::Item> {
-        if self.index >= self.array.len() {
+        if self.index >= self.len {
             return None;
-        } else {
-            self.index += 1;
         }
-        Some(unsafe { self.array.value_unchecked(self.index - 1) })
+        let index = self.index;
+        let r = Some(unsafe {
+            std::slice::from_raw_parts(
+                self.array.values().as_ptr().add(index * self.array.size()),
+                self.array.size(),
+            )
+        });
+        self.index += 1;
+        r
     }
 
     fn size_hint(&self) -> (usize, Option<usize>) {
-        (
-            self.array.len() - self.index,
-            Some(self.array.len() - self.index),
-        )
+        (self.len - self.index, Some(self.len - self.index))
     }
 }
 
 impl<'a> IntoIterator for &'a FixedSizeBinaryArray {
     type Item = Option<&'a [u8]>;
-    type IntoIter = ZipValidity<'a, &'a [u8], FixedSizeBinaryValuesIter<'a>>;
+    type IntoIter = ZipValidity<'a, &'a [u8], FixedSizeBinaryValuesIter<'a, FixedSizeBinaryArray>>;
 
     fn into_iter(self) -> Self::IntoIter {
         self.iter()
@@ -49,10 +57,34 @@ impl<'a> IntoIterator for &'a FixedSizeBinaryArray {
 
 impl<'a> FixedSizeBinaryArray {
     /// constructs a new iterator
-    pub fn iter(&'a self) -> ZipValidity<'a, &'a [u8], FixedSizeBinaryValuesIter<'a>> {
+    pub fn iter(
+        &'a self,
+    ) -> ZipValidity<'a, &'a [u8], FixedSizeBinaryValuesIter<'a, FixedSizeBinaryArray>> {
         zip_validity(
             FixedSizeBinaryValuesIter::new(self),
             self.validity.as_ref().map(|x| x.iter()),
         )
     }
 }
+
+impl<'a> IntoIterator for &'a MutableFixedSizeBinaryArray {
+    type Item = Option<&'a [u8]>;
+    type IntoIter =
+        ZipValidity<'a, &'a [u8], FixedSizeBinaryValuesIter<'a, MutableFixedSizeBinaryArray>>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.iter()
+    }
+}
+
+impl<'a> MutableFixedSizeBinaryArray {
+    /// constructs a new iterator
+    pub fn iter(
+        &'a self,
+    ) -> ZipValidity<'a, &'a [u8], FixedSizeBinaryValuesIter<'a, MutableFixedSizeBinaryArray>> {
+        zip_validity(
+            FixedSizeBinaryValuesIter::new(self),
+            self.validity().as_ref().map(|x| x.iter()),
+        )
+    }
+}
diff --git a/src/array/fixed_size_binary/mod.rs b/src/array/fixed_size_binary/mod.rs
index ee4405cfd2..714dccc8cc 100644
--- a/src/array/fixed_size_binary/mod.rs
+++ b/src/array/fixed_size_binary/mod.rs
@@ -161,3 +161,20 @@ impl FixedSizeBinaryArray {
             .into()
     }
 }
+
+pub trait FixedSizeBinaryValues {
+    fn values(&self) -> &[u8];
+    fn size(&self) -> usize;
+}
+
+impl FixedSizeBinaryValues for FixedSizeBinaryArray {
+    #[inline]
+    fn values(&self) -> &[u8] {
+        &self.values
+    }
+
+    #[inline]
+    fn size(&self) -> usize {
+        self.size as usize
+    }
+}
diff --git a/src/array/fixed_size_binary/mutable.rs b/src/array/fixed_size_binary/mutable.rs
index 395ecfca3c..4f06eae347 100644
--- a/src/array/fixed_size_binary/mutable.rs
+++ b/src/array/fixed_size_binary/mutable.rs
@@ -8,7 +8,7 @@ use crate::{
     error::{ArrowError, Result},
 };
 
-use super::FixedSizeBinaryArray;
+use super::{FixedSizeBinaryArray, FixedSizeBinaryValues};
 
 /// Mutable version of [`FixedSizeBinaryArray`].
 #[derive(Debug)]
@@ -30,19 +30,44 @@ impl From<MutableFixedSizeBinaryArray> for FixedSizeBinaryArray {
 }
 
 impl MutableFixedSizeBinaryArray {
+    pub fn from_data(
+        size: usize,
+        values: MutableBuffer<u8>,
+        validity: Option<MutableBitmap>,
+    ) -> Self {
+        assert_eq!(
+            values.len() % size,
+            0,
+            "The len of values must be a multiple of size"
+        );
+        if let Some(validity) = &validity {
+            assert_eq!(
+                validity.len(),
+                values.len() / size,
+                "The len of the validity must be equal to values / size"
+            );
+        }
+        Self {
+            data_type: DataType::FixedSizeBinary(size as i32),
+            size,
+            values,
+            validity,
+        }
+    }
+
     pub fn new(size: usize) -> Self {
         Self::with_capacity(size, 0)
     }
 
     pub fn with_capacity(size: usize, capacity: usize) -> Self {
-        Self {
-            data_type: DataType::FixedSizeBinary(size as i32),
+        Self::from_data(
             size,
-            values: MutableBuffer::<u8>::with_capacity(capacity * size),
-            validity: None,
-        }
+            MutableBuffer::<u8>::with_capacity(capacity * size),
+            None,
+        )
     }
 
+    #[inline]
     pub fn try_push<P: AsRef<[u8]>>(&mut self, value: Option<P>) -> Result<()> {
         match value {
             Some(bytes) => {
@@ -89,11 +114,37 @@ impl MutableFixedSizeBinaryArray {
     }
 
     fn init_validity(&mut self) {
-        self.validity = Some(MutableBitmap::from_trusted_len_iter(
-            std::iter::repeat(true)
-                .take(self.len() - 1)
-                .chain(std::iter::once(false)),
-        ))
+        let mut validity = MutableBitmap::new();
+        validity.extend_constant(self.len(), true);
+        validity.set(self.len() - 1, false);
+        self.validity = Some(validity)
+    }
+
+    /// Returns the element at index `i` as `&[u8]`
+    #[inline]
+    pub fn value(&self, i: usize) -> &[u8] {
+        &self.values[i * self.size..(i + 1) * self.size]
+    }
+
+    /// Returns the element at index `i` as `&[u8]`
+    /// # Safety
+    /// Assumes that the `i < self.len`.
+    #[inline]
+    pub unsafe fn value_unchecked(&self, i: usize) -> &[u8] {
+        std::slice::from_raw_parts(self.values.as_ptr().add(i * self.size), self.size)
+    }
+}
+
+/// Accessors
+impl MutableFixedSizeBinaryArray {
+    /// Returns its values.
+    pub fn values(&self) -> &MutableBuffer<u8> {
+        &self.values
+    }
+
+    /// Returns a mutable slice of values.
+    pub fn values_mut_slice(&mut self) -> &mut [u8] {
+        self.values.as_mut_slice()
     }
 }
 
@@ -130,3 +181,21 @@ impl MutableArray for MutableFixedSizeBinaryArray {
         self.values.extend_constant(self.size, 0);
     }
 }
+
+impl FixedSizeBinaryValues for MutableFixedSizeBinaryArray {
+    #[inline]
+    fn values(&self) -> &[u8] {
+        &self.values
+    }
+
+    #[inline]
+    fn size(&self) -> usize {
+        self.size
+    }
+}
+
+impl PartialEq for MutableFixedSizeBinaryArray {
+    fn eq(&self, other: &Self) -> bool {
+        self.iter().eq(other.iter())
+    }
+}
diff --git a/src/array/primitive/mutable.rs b/src/array/primitive/mutable.rs
index a4f771ebe3..9e126ee485 100644
--- a/src/array/primitive/mutable.rs
+++ b/src/array/primitive/mutable.rs
@@ -173,11 +173,10 @@ impl<T: NativeType> MutablePrimitiveArray<T> {
     }
 
     fn init_validity(&mut self) {
-        self.validity = Some(MutableBitmap::from_trusted_len_iter(
-            std::iter::repeat(true)
-                .take(self.len() - 1)
-                .chain(std::iter::once(false)),
-        ))
+        let mut validity = MutableBitmap::new();
+        validity.extend_constant(self.len(), true);
+        validity.set(self.len() - 1, false);
+        self.validity = Some(validity)
     }
 
     /// Changes the arrays' [`DataType`], returning a new [`MutablePrimitiveArray`].
diff --git a/src/compute/comparison/mod.rs b/src/compute/comparison/mod.rs
index 40bf8e1ca6..69d7ed07b1 100644
--- a/src/compute/comparison/mod.rs
+++ b/src/compute/comparison/mod.rs
@@ -30,6 +30,9 @@ mod boolean;
 mod primitive;
 mod utf8;
 
+mod simd;
+pub use simd::{Simd8, Simd8Lanes};
+
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub enum Operator {
     Lt,
diff --git a/src/compute/comparison/primitive.rs b/src/compute/comparison/primitive.rs
index e84f7a436b..04dc5aa078 100644
--- a/src/compute/comparison/primitive.rs
+++ b/src/compute/comparison/primitive.rs
@@ -23,45 +23,33 @@ use crate::{
     error::{ArrowError, Result},
 };
 
+use super::simd::{Simd8, Simd8Lanes};
 use super::{super::utils::combine_validities, Operator};
 
 pub(crate) fn compare_values_op<T, F>(lhs: &[T], rhs: &[T], op: F) -> MutableBitmap
 where
-    T: NativeType,
-    F: Fn(T, T) -> bool,
+    T: NativeType + Simd8,
+    F: Fn(T::Simd, T::Simd) -> u8,
 {
     assert_eq!(lhs.len(), rhs.len());
-    let mut values = MutableBuffer::from_len_zeroed((lhs.len() + 7) / 8);
 
     let lhs_chunks_iter = lhs.chunks_exact(8);
     let lhs_remainder = lhs_chunks_iter.remainder();
     let rhs_chunks_iter = rhs.chunks_exact(8);
     let rhs_remainder = rhs_chunks_iter.remainder();
 
-    let chunks = lhs.len() / 8;
-
-    values[..chunks]
-        .iter_mut()
-        .zip(lhs_chunks_iter)
-        .zip(rhs_chunks_iter)
-        .for_each(|((byte, lhs), rhs)| {
-            lhs.iter()
-                .zip(rhs.iter())
-                .enumerate()
-                .for_each(|(i, (&lhs, &rhs))| {
-                    *byte |= if op(lhs, rhs) { 1 << i } else { 0 };
-                });
-        });
+    let mut values = MutableBuffer::with_capacity((lhs.len() + 7) / 8);
+    let iterator = lhs_chunks_iter.zip(rhs_chunks_iter).map(|(lhs, rhs)| {
+        let lhs = T::Simd::from_chunk(lhs);
+        let rhs = T::Simd::from_chunk(rhs);
+        op(lhs, rhs)
+    });
+    values.extend_from_trusted_len_iter(iterator);
 
     if !lhs_remainder.is_empty() {
-        let last = &mut values[chunks];
-        lhs_remainder
-            .iter()
-            .zip(rhs_remainder.iter())
-            .enumerate()
-            .for_each(|(i, (&lhs, &rhs))| {
-                *last |= if op(lhs, rhs) { 1 << i } else { 0 };
-            });
+        let lhs = T::Simd::from_incomplete_chunk(lhs_remainder, T::default());
+        let rhs = T::Simd::from_incomplete_chunk(rhs_remainder, T::default());
+        values.push(op(lhs, rhs))
     };
     MutableBitmap::from_buffer(values, lhs.len())
 }
@@ -70,8 +58,8 @@ where
 /// comparison function.
 fn compare_op<T, F>(lhs: &PrimitiveArray<T>, rhs: &PrimitiveArray<T>, op: F) -> Result<BooleanArray>
 where
-    T: NativeType,
-    F: Fn(T, T) -> bool,
+    T: NativeType + Simd8,
+    F: Fn(T::Simd, T::Simd) -> u8,
 {
     if lhs.len() != rhs.len() {
         return Err(ArrowError::InvalidArgumentError(
@@ -90,31 +78,25 @@ where
 /// a specified comparison function.
 pub fn compare_op_scalar<T, F>(lhs: &PrimitiveArray<T>, rhs: T, op: F) -> Result<BooleanArray>
 where
-    T: NativeType,
-    F: Fn(T, T) -> bool,
+    T: NativeType + Simd8,
+    F: Fn(T::Simd, T::Simd) -> u8,
 {
     let validity = lhs.validity().clone();
-
-    let mut values = MutableBuffer::from_len_zeroed((lhs.len() + 7) / 8);
+    let rhs = T::Simd::from_chunk(&[rhs; 8]);
 
     let lhs_chunks_iter = lhs.values().chunks_exact(8);
     let lhs_remainder = lhs_chunks_iter.remainder();
-    let chunks = lhs.len() / 8;
 
-    values[..chunks]
-        .iter_mut()
-        .zip(lhs_chunks_iter)
-        .for_each(|(byte, chunk)| {
-            chunk.iter().enumerate().for_each(|(i, &c_i)| {
-                *byte |= if op(c_i, rhs) { 1 << i } else { 0 };
-            });
-        });
+    let mut values = MutableBuffer::with_capacity((lhs.len() + 7) / 8);
+    let iterator = lhs_chunks_iter.map(|lhs| {
+        let lhs = T::Simd::from_chunk(lhs);
+        op(lhs, rhs)
+    });
+    values.extend_from_trusted_len_iter(iterator);
 
     if !lhs_remainder.is_empty() {
-        let last = &mut values[chunks];
-        lhs_remainder.iter().enumerate().for_each(|(i, &lhs)| {
-            *last |= if op(lhs, rhs) { 1 << i } else { 0 };
-        });
+        let lhs = T::Simd::from_incomplete_chunk(lhs_remainder, T::default());
+        values.push(op(lhs, rhs))
     };
 
     Ok(BooleanArray::from_data(
@@ -126,105 +108,105 @@ where
 /// Perform `lhs == rhs` operation on two arrays.
 pub fn eq<T>(lhs: &PrimitiveArray<T>, rhs: &PrimitiveArray<T>) -> Result<BooleanArray>
 where
-    T: NativeType,
+    T: NativeType + Simd8,
 {
-    compare_op(lhs, rhs, |a, b| a == b)
+    compare_op(lhs, rhs, |a, b| a.eq(b))
 }
 
 /// Perform `left == right` operation on an array and a scalar value.
 pub fn eq_scalar<T>(lhs: &PrimitiveArray<T>, rhs: T) -> Result<BooleanArray>
 where
-    T: NativeType,
+    T: NativeType + Simd8,
 {
-    compare_op_scalar(lhs, rhs, |a, b| a == b)
+    compare_op_scalar(lhs, rhs, |a, b| a.eq(b))
 }
 
 /// Perform `left != right` operation on two arrays.
 pub fn neq<T>(lhs: &PrimitiveArray<T>, rhs: &PrimitiveArray<T>) -> Result<BooleanArray>
 where
-    T: NativeType,
+    T: NativeType + Simd8,
 {
-    compare_op(lhs, rhs, |a, b| a != b)
+    compare_op(lhs, rhs, |a, b| a.neq(b))
 }
 
 /// Perform `left != right` operation on an array and a scalar value.
 pub fn neq_scalar<T>(lhs: &PrimitiveArray<T>, rhs: T) -> Result<BooleanArray>
 where
-    T: NativeType,
+    T: NativeType + Simd8,
 {
-    compare_op_scalar(lhs, rhs, |a, b| a != b)
+    compare_op_scalar(lhs, rhs, |a, b| a.neq(b))
 }
 
 /// Perform `left < right` operation on two arrays.
 pub fn lt<T>(lhs: &PrimitiveArray<T>, rhs: &PrimitiveArray<T>) -> Result<BooleanArray>
 where
-    T: NativeType + std::cmp::PartialOrd,
+    T: NativeType + Simd8,
 {
-    compare_op(lhs, rhs, |a, b| a < b)
+    compare_op(lhs, rhs, |a, b| a.lt(b))
 }
 
 /// Perform `left < right` operation on an array and a scalar value.
 pub fn lt_scalar<T>(lhs: &PrimitiveArray<T>, rhs: T) -> Result<BooleanArray>
 where
-    T: NativeType + std::cmp::PartialOrd,
+    T: NativeType + Simd8,
 {
-    compare_op_scalar(lhs, rhs, |a, b| a < b)
+    compare_op_scalar(lhs, rhs, |a, b| a.lt(b))
 }
 
 /// Perform `left <= right` operation on two arrays.
 pub fn lt_eq<T>(lhs: &PrimitiveArray<T>, rhs: &PrimitiveArray<T>) -> Result<BooleanArray>
 where
-    T: NativeType + std::cmp::PartialOrd,
+    T: NativeType + Simd8,
 {
-    compare_op(lhs, rhs, |a, b| a <= b)
+    compare_op(lhs, rhs, |a, b| a.lt_eq(b))
 }
 
 /// Perform `left <= right` operation on an array and a scalar value.
 /// Null values are less than non-null values.
 pub fn lt_eq_scalar<T>(lhs: &PrimitiveArray<T>, rhs: T) -> Result<BooleanArray>
 where
-    T: NativeType + std::cmp::PartialOrd,
+    T: NativeType + Simd8,
 {
-    compare_op_scalar(lhs, rhs, |a, b| a <= b)
+    compare_op_scalar(lhs, rhs, |a, b| a.lt_eq(b))
 }
 
 /// Perform `left > right` operation on two arrays. Non-null values are greater than null
 /// values.
 pub fn gt<T>(lhs: &PrimitiveArray<T>, rhs: &PrimitiveArray<T>) -> Result<BooleanArray>
 where
-    T: NativeType + std::cmp::PartialOrd,
+    T: NativeType + Simd8,
 {
-    compare_op(lhs, rhs, |a, b| a > b)
+    compare_op(lhs, rhs, |a, b| a.gt(b))
 }
 
 /// Perform `left > right` operation on an array and a scalar value.
 /// Non-null values are greater than null values.
 pub fn gt_scalar<T>(lhs: &PrimitiveArray<T>, rhs: T) -> Result<BooleanArray>
 where
-    T: NativeType + std::cmp::PartialOrd,
+    T: NativeType + Simd8,
 {
-    compare_op_scalar(lhs, rhs, |a, b| a > b)
+    compare_op_scalar(lhs, rhs, |a, b| a.gt(b))
 }
 
 /// Perform `left >= right` operation on two arrays. Non-null values are greater than null
 /// values.
 pub fn gt_eq<T>(lhs: &PrimitiveArray<T>, rhs: &PrimitiveArray<T>) -> Result<BooleanArray>
 where
-    T: NativeType + std::cmp::PartialOrd,
+    T: NativeType + Simd8,
 {
-    compare_op(lhs, rhs, |a, b| a >= b)
+    compare_op(lhs, rhs, |a, b| a.gt_eq(b))
 }
 
 /// Perform `left >= right` operation on an array and a scalar value.
 /// Non-null values are greater than null values.
 pub fn gt_eq_scalar<T>(lhs: &PrimitiveArray<T>, rhs: T) -> Result<BooleanArray>
 where
-    T: NativeType + std::cmp::PartialOrd,
+    T: NativeType + Simd8,
 {
-    compare_op_scalar(lhs, rhs, |a, b| a >= b)
+    compare_op_scalar(lhs, rhs, |a, b| a.gt_eq(b))
 }
 
-pub fn compare<T: NativeType + std::cmp::PartialOrd>(
+pub fn compare<T: NativeType + Simd8>(
     lhs: &PrimitiveArray<T>,
     rhs: &PrimitiveArray<T>,
     op: Operator,
@@ -239,7 +221,7 @@ pub fn compare<T: NativeType + std::cmp::PartialOrd>(
     }
 }
 
-pub fn compare_scalar<T: NativeType + std::cmp::PartialOrd>(
+pub fn compare_scalar<T: NativeType + Simd8>(
     lhs: &PrimitiveArray<T>,
     rhs: T,
     op: Operator,
diff --git a/src/compute/comparison/simd/mod.rs b/src/compute/comparison/simd/mod.rs
new file mode 100644
index 0000000000..3fe82b81db
--- /dev/null
+++ b/src/compute/comparison/simd/mod.rs
@@ -0,0 +1,91 @@
+use crate::types::NativeType;
+
+/// [`NativeType`] that supports a representation of 8 lanes
+pub trait Simd8: NativeType {
+    type Simd: Simd8Lanes<Self>;
+}
+
+pub trait Simd8Lanes<T>: Copy {
+    fn from_chunk(v: &[T]) -> Self;
+    fn from_incomplete_chunk(v: &[T], remaining: T) -> Self;
+    fn eq(self, other: Self) -> u8;
+    fn neq(self, other: Self) -> u8;
+    fn lt_eq(self, other: Self) -> u8;
+    fn lt(self, other: Self) -> u8;
+    fn gt(self, other: Self) -> u8;
+    fn gt_eq(self, other: Self) -> u8;
+}
+
+#[inline]
+pub(super) fn set<T: Copy, F: Fn(T, T) -> bool>(lhs: [T; 8], rhs: [T; 8], op: F) -> u8 {
+    let mut byte = 0u8;
+    lhs.iter()
+        .zip(rhs.iter())
+        .enumerate()
+        .for_each(|(i, (lhs, rhs))| {
+            byte |= if op(*lhs, *rhs) { 1 << i } else { 0 };
+        });
+    byte
+}
+
+macro_rules! simd8_native {
+    ($type:ty) => {
+        impl Simd8 for $type {
+            type Simd = [$type; 8];
+        }
+
+        impl Simd8Lanes<$type> for [$type; 8] {
+            #[inline]
+            fn from_chunk(v: &[$type]) -> Self {
+                v.try_into().unwrap()
+            }
+
+            #[inline]
+            fn from_incomplete_chunk(v: &[$type], remaining: $type) -> Self {
+                let mut a = [remaining; 8];
+                a.iter_mut().zip(v.iter()).for_each(|(a, b)| *a = *b);
+                a
+            }
+
+            #[inline]
+            fn eq(self, other: Self) -> u8 {
+                set(self, other, |x, y| x == y)
+            }
+
+            #[inline]
+            fn neq(self, other: Self) -> u8 {
+                #[allow(clippy::float_cmp)]
+                set(self, other, |x, y| x != y)
+            }
+
+            #[inline]
+            fn lt_eq(self, other: Self) -> u8 {
+                set(self, other, |x, y| x <= y)
+            }
+
+            #[inline]
+            fn lt(self, other: Self) -> u8 {
+                set(self, other, |x, y| x < y)
+            }
+
+            #[inline]
+            fn gt_eq(self, other: Self) -> u8 {
+                set(self, other, |x, y| x >= y)
+            }
+
+            #[inline]
+            fn gt(self, other: Self) -> u8 {
+                set(self, other, |x, y| x > y)
+            }
+        }
+    };
+}
+
+#[cfg(not(feature = "simd"))]
+mod native;
+#[cfg(not(feature = "simd"))]
+pub use native::*;
+#[cfg(feature = "simd")]
+mod packed;
+#[cfg(feature = "simd")]
+pub use packed::*;
diff --git a/src/compute/comparison/simd/native.rs b/src/compute/comparison/simd/native.rs
new file mode 100644
index 0000000000..bc3e820e60
--- /dev/null
+++ b/src/compute/comparison/simd/native.rs
@@ -0,0 +1,15 @@
+use std::convert::TryInto;
+
+use super::{set, Simd8, Simd8Lanes};
+
+simd8_native!(u8);
+simd8_native!(u16);
+simd8_native!(u32);
+simd8_native!(u64);
+simd8_native!(i8);
+simd8_native!(i16);
+simd8_native!(i32);
+simd8_native!(i128);
+simd8_native!(i64);
+simd8_native!(f32);
+simd8_native!(f64);
diff --git a/src/compute/comparison/simd/packed.rs b/src/compute/comparison/simd/packed.rs
new file mode 100644
index 0000000000..1e9b250c73
--- /dev/null
+++ b/src/compute/comparison/simd/packed.rs
@@ -0,0 +1,69 @@
+use std::convert::TryInto;
+
+use super::{set, Simd8, Simd8Lanes};
+
+use packed_simd::*;
+
+macro_rules! simd8 {
+    ($type:ty, $md:ty) => {
+        impl Simd8 for $type {
+            type Simd = $md;
+        }
+
+        impl Simd8Lanes<$type> for $md {
+            #[inline]
+            fn from_chunk(v: &[$type]) -> Self {
+                <$md>::from_slice_aligned(v)
+            }
+
+            #[inline]
+            fn from_incomplete_chunk(v: &[$type], remaining: $type) -> Self {
+                let mut a = [remaining; 8];
+                a.iter_mut().zip(v.iter()).for_each(|(a, b)| *a = *b);
+                Self::from_chunk(a.as_ref())
+            }
+
+            #[inline]
+            fn eq(self, other: Self) -> u8 {
+                self.eq(other).bitmask()
+            }
+
+            #[inline]
+            fn neq(self, other: Self) -> u8 {
+                self.ne(other).bitmask()
+            }
+
+            #[inline]
+            fn lt_eq(self, other: Self) -> u8 {
+                self.le(other).bitmask()
+            }
+
+            #[inline]
+            fn lt(self, other: Self) -> u8 {
+                self.lt(other).bitmask()
+            }
+
+            #[inline]
+            fn gt_eq(self, other: Self) -> u8 {
+                self.ge(other).bitmask()
+            }
+
+            #[inline]
+            fn gt(self, other: Self) -> u8 {
+                self.gt(other).bitmask()
+            }
+        }
+    };
+}
+
+simd8!(u8, u8x8);
+simd8!(u16, u16x8);
+simd8!(u32, u32x8);
+simd8!(u64, u64x8);
+simd8!(i8, i8x8);
+simd8!(i16, i16x8);
+simd8!(i32, i32x8);
+simd8!(i64, i64x8);
+simd8_native!(i128);
+simd8!(f32, f32x8);
+simd8!(f64, f64x8);
diff --git a/src/compute/nullif.rs b/src/compute/nullif.rs
index 8d2022e85e..1b41b9e478 100644
--- a/src/compute/nullif.rs
+++ b/src/compute/nullif.rs
@@ -1,5 +1,5 @@
 use crate::array::PrimitiveArray;
-use crate::compute::comparison::primitive_compare_values_op;
+use crate::compute::comparison::{primitive_compare_values_op, Simd8, Simd8Lanes};
 use crate::datatypes::DataType;
 use crate::error::{ArrowError, Result};
 use crate::{array::Array, types::NativeType};
@@ -29,7 +29,7 @@ use super::utils::combine_validities;
 /// This function errors iff
 /// * The arguments do not have the same logical type
 /// * The arguments do not have the same length
-pub fn nullif_primitive<T: NativeType>(
+pub fn nullif_primitive<T: NativeType + Simd8>(
     lhs: &PrimitiveArray<T>,
     rhs: &PrimitiveArray<T>,
 ) -> Result<PrimitiveArray<T>> {
@@ -39,7 +39,7 @@ pub fn nullif_primitive<T: NativeType>(
         ));
     }
 
-    let equal = primitive_compare_values_op(lhs.values(), rhs.values(), |lhs, rhs| lhs != rhs);
+    let equal = primitive_compare_values_op(lhs.values(), rhs.values(), |lhs, rhs| lhs.neq(rhs));
     let equal = equal.into();
 
     let validity = combine_validities(lhs.validity(), &equal);
diff --git a/src/io/json/read/deserialize.rs b/src/io/json/read/deserialize.rs
index 18c7499861..03fb257a27 100644
--- a/src/io/json/read/deserialize.rs
+++ b/src/io/json/read/deserialize.rs
@@ -25,10 +25,7 @@ use serde_json::Value;
 
 use crate::types::NaturalDataType;
 use crate::{
-    array::{
-        Array, BooleanArray, DictionaryArray, DictionaryKey, ListArray, NullArray, Offset,
-        PrimitiveArray, StructArray, Utf8Array,
-    },
+    array::*,
     bitmap::MutableBitmap,
     buffer::MutableBuffer,
     datatypes::{DataType, IntervalUnit},
@@ -90,6 +87,14 @@ fn read_primitive<T: NativeType + NaturalDataType + NumCast>(
     PrimitiveArray::from_trusted_len_iter(iter).to(data_type)
 }
 
+fn read_binary<O: Offset>(rows: &[&Value]) -> BinaryArray<O> {
+    let iter = rows.iter().map(|row| match row {
+        Value::String(v) => Some(v.as_bytes()),
+        _ => None,
+    });
+    BinaryArray::from_trusted_len_iter(iter)
+}
+
 fn read_boolean(rows: &[&Value]) -> BooleanArray {
     let iter = rows.iter().map(|row| match row {
         Value::Bool(v) => Some(v),
@@ -236,9 +241,8 @@ pub fn read(rows: &[&Value], data_type: DataType) -> Arc<dyn Array> {
         DataType::LargeUtf8 => Arc::new(read_utf8::<i64>(rows)),
         DataType::List(_) => Arc::new(read_list::<i32>(rows, data_type)),
         DataType::LargeList(_) => Arc::new(read_list::<i64>(rows, data_type)),
-        // unsupported
-        //DataType::Binary => Box::new(BinaryArray::<i32>::new_empty()),
-        //DataType::LargeBinary => Box::new(BinaryArray::<i64>::new_empty()),
+        DataType::Binary => Arc::new(read_binary::<i32>(rows)),
+        DataType::LargeBinary => Arc::new(read_binary::<i64>(rows)),
         DataType::Struct(_) => Arc::new(read_struct(rows, data_type)),
         DataType::Dictionary(key_type, _) => match key_type.as_ref() {
             DataType::Int8 => Arc::new(read_dictionary::<i8>(rows, data_type)),
diff --git a/src/trusted_len.rs b/src/trusted_len.rs
index 877a180c9a..8c52c0bf86 100644
--- a/src/trusted_len.rs
+++ b/src/trusted_len.rs
@@ -26,6 +26,8 @@ where
 {
 }
 
+unsafe impl<T> TrustedLen for std::slice::ChunksExact<'_, T> {}
+
 unsafe impl<T> TrustedLen for std::slice::Windows<'_, T> {}
 
 unsafe impl<A, B> TrustedLen for std::iter::Chain<A, B>


Test patch:
diff --git a/tests/it/array/fixed_size_binary/mutable.rs b/tests/it/array/fixed_size_binary/mutable.rs
index 064fcaddda..d8c27ccee5 100644
--- a/tests/it/array/fixed_size_binary/mutable.rs
+++ b/tests/it/array/fixed_size_binary/mutable.rs
@@ -1,8 +1,44 @@
 use arrow2::array::*;
-use arrow2::bitmap::Bitmap;
+use arrow2::bitmap::{Bitmap, MutableBitmap};
+use arrow2::buffer::MutableBuffer;
+use arrow2::datatypes::DataType;
 
 #[test]
 fn basic() {
+    let a = MutableFixedSizeBinaryArray::from_data(2, MutableBuffer::from([1, 2, 3, 4]), None);
+    assert_eq!(a.len(), 2);
+    assert_eq!(a.data_type(), &DataType::FixedSizeBinary(2));
+    assert_eq!(a.values(), &MutableBuffer::from([1, 2, 3, 4]));
+    assert_eq!(a.validity(), &None);
+    assert_eq!(a.value(1), &[3, 4]);
+    assert_eq!(unsafe { a.value_unchecked(1) }, &[3, 4]);
+}
+
+#[allow(clippy::eq_op)]
+#[test]
+fn equal() {
+    let a = MutableFixedSizeBinaryArray::from_data(2, MutableBuffer::from([1, 2, 3, 4]), None);
+    assert_eq!(a, a);
+    let b = MutableFixedSizeBinaryArray::from_data(2, MutableBuffer::from([1, 2]), None);
+    assert_eq!(b, b);
+    assert!(a != b);
+    let a = MutableFixedSizeBinaryArray::from_data(
+        2,
+        MutableBuffer::from([1, 2, 3, 4]),
+        Some(MutableBitmap::from([true, false])),
+    );
+    let b = MutableFixedSizeBinaryArray::from_data(
+        2,
+        MutableBuffer::from([1, 2, 3, 4]),
+        Some(MutableBitmap::from([false, true])),
+    );
+    assert_eq!(a, a);
+    assert_eq!(b, b);
+    assert!(a != b);
+}
+
+#[test]
+fn try_from_iter() {
     let array = MutableFixedSizeBinaryArray::try_from_iter(
         vec![Some(b"ab"), Some(b"bc"), None, Some(b"fh")],
         2,
diff --git a/tests/it/io/json/mod.rs b/tests/it/io/json/mod.rs
index 37810e9ec3..b8bc9a5f7b 100644
--- a/tests/it/io/json/mod.rs
+++ b/tests/it/io/json/mod.rs
@@ -167,8 +167,8 @@ fn case_basics() -> (String, Schema, Vec<Box<dyn Array>>) {
 }
 
 fn case_basics_schema() -> (String, Schema, Vec<Box<dyn Array>>) {
-    let data = r#"{"a":1, "b":2.0, "c":false, "d":"4"}
-    {"a":10, "b":-3.5, "c":true, "d":null}
+    let data = r#"{"a":1, "b":2.0, "c":false, "d":"4", "e":"4"}
+    {"a":10, "b":-3.5, "c":true, "d":null, "e":"text"}
     {"a":100000000, "b":0.6, "d":"text"}"#
         .to_string();
     let schema = Schema::new(vec![
@@ -176,11 +176,17 @@ fn case_basics_schema() -> (String, Schema, Vec<Box<dyn Array>>) {
         Field::new("b", DataType::Float32, true),
         Field::new("c", DataType::Boolean, true),
         // note how "d" is not here
+        Field::new("e", DataType::Binary, true),
     ]);
     let columns = vec![
         Box::new(UInt32Array::from_slice(&[1, 10, 100000000])) as Box<dyn Array>,
         Box::new(Float32Array::from_slice(&[2.0, -3.5, 0.6])),
         Box::new(BooleanArray::from(&[Some(false), Some(true), None])),
+        Box::new(BinaryArray::<i32>::from(&[
+            Some(b"4".as_ref()),
+            Some(b"text".as_ref()),
+            None,
+        ])),
     ];
     (data, schema, columns)
 }