diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7ec6564..fa38845 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,9 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 ## [Unreleased]
 
+### Changed
+  - Geometric products of `BitIndex` objects are faster.
+
 ## [0.1.7] - 2024-09-11
 
 ### Added
diff --git a/src/bitindex.jl b/src/bitindex.jl
index f699833..92bd976 100644
--- a/src/bitindex.jl
+++ b/src/bitindex.jl
@@ -4,8 +4,8 @@
 Generates a signmask, or a string of bits where the only 1 bit is the sign bit. If `signbit` is set
 to false, this returns zero (or whatever value is represented by all bits being 0).
 """
-signmask(T::Type{<:Integer}, signbit::Bool = true) = bitreverse(T(signbit))
-signmask(x::Integer, signbit::Bool = true) = bitreverse(typeof(x)(signbit))
+@inline signmask(T::Type{<:Integer}, signbit::Bool = true) = bitreverse(T(signbit))
+@inline signmask(x::Integer, signbit::Bool = true) = bitreverse(typeof(x)(signbit))
 
 """
     BitIndex{Q}
@@ -74,7 +74,7 @@ function _bitindex(::Val{S}, t::NTuple) where S
     (t, parity) = _sort_with_parity(t)
     i = signmask(UInt, parity)
     for x in t
-        i = xor(i, 2^(x - firstindex(S)))
+        i = xor(i, <<(1, x - firstindex(S)))
     end
     return BitIndex{S}(i)
 end
@@ -100,7 +100,7 @@ function show(io::IO, b::BitIndex{Q}) where Q
     iszero(UInt(b)) && return nothing
     found_first_vector = false
     for a in 1:min(dimension(Q), 8*sizeof(UInt) - 1)
-        if !iszero(UInt(b) & 2^(a-1))
+        if !iszero(UInt(b) & <<(1, a-1))
             found_first_vector && print(io, ", ")
             print(io, eachindex(Q)[a])
             found_first_vector = true
@@ -185,16 +185,18 @@ conj(i::BitIndex) = typeof(i)(xor(signbit(i), !iszero((grade(i) + 1) & 2)), UInt
 
 #---Multiplication tools---------------------------------------------------------------------------#
 
+# NOTE: <<(UInt(1), x) is faster/easier to inline than UInt(2)^x
+
 function positive_square_bits(S::Metrics.AbstractSignature)
-    return sum((S[x] === Int8(+1)) * UInt(2)^(x - firstindex(S)) for x in eachindex(S))
+    return sum((S[x] === Int8(+1)) * <<(UInt(1), x - firstindex(S)) for x in eachindex(S))
 end
 
 function negative_square_bits(S::Metrics.AbstractSignature)
-    return sum((S[x] === Int8(-1)) * UInt(2)^(x - firstindex(S)) for x in eachindex(S))
+    return sum((S[x] === Int8(-1)) * <<(UInt(1), x - firstindex(S)) for x in eachindex(S))
 end
 
 function zero_square_bits(S::Metrics.AbstractSignature)
-    return sum((S[x] === Int8(0)) * UInt(2)^(x - firstindex(S)) for x in eachindex(S))
+    return sum((S[x] === Int8(0)) * <<(UInt(1), x - firstindex(S)) for x in eachindex(S))
 end
 
 """
@@ -237,7 +239,7 @@ As with `Base.signbit()`, `true` represents a negative sign and `false` a positi
 in degenerate metrics (such as those of projective geometric algebras) the sign bit may be
 irrelevant as the multiplication of those basis blades would result in zero.
 """
-function signbit_of_mult(a::Unsigned, b::Unsigned)
+@inline function signbit_of_mult(a::Unsigned, b::Unsigned)
     a = abs(a) >>> 1
     sum = 0
     while !iszero(a)
@@ -250,7 +252,7 @@ end
 # Account for the sign bits of signed integers
 signbit_of_mult(a::Integer, b::Integer) = xor(signbit_of_mult(unsigned.(a,b)...), signbit(xor(a,b)))
 
-function signbit_of_mult(a::BitIndex{Q}, b::BitIndex{Q}) where Q
+@inline function signbit_of_mult(a::BitIndex{Q}, b::BitIndex{Q}) where Q
     base_signbit = xor(signbit_of_mult(UInt(a), UInt(b)), signbit(a), signbit(b))
     return xor(base_signbit, isodious(UInt(a) & UInt(b) & negative_square_bits(Q)))
 end
@@ -261,7 +263,7 @@ end
 Returns `false` if the product of `a` and `b` is zero due to the squaring of a degenerate component,
 `true` otherwise. This function always returns `true` if `R === 0`.
 """
-function nondegenerate_mult(a::BitIndex{Q}, b::BitIndex{Q}) where Q
+@inline function nondegenerate_mult(a::BitIndex{Q}, b::BitIndex{Q}) where Q
     return iszero(UInt(a) & UInt(b) & zero_square_bits(Q))
 end