How can I make my md5 implementation blazing fast?

```rust
fn md5_f(x: u32, y: u32, z: u32) -> u32 {
    (x & y) | ((!x) & z)
}

fn md5_g(x: u32, y: u32, z: u32) -> u32 {
    (x & z) | (y & (!z))
}

fn md5_h(x: u32, y: u32, z: u32) -> u32 {
    x ^ y ^ z
}

fn md5_i(x: u32, y: u32, z: u32) -> u32 {
    y ^ (x | (!z))
}

fn md5_bytes(big_input: &[u8]) -> [u8; 16] {
    let bits_per_byte = 8;
    let num_bytes_per_chunk = 512 / bits_per_byte;
    let max_actual_bits_in_last_chunk = 512 - 64;
    let max_actual_bytes_in_last_chunk = max_actual_bits_in_last_chunk / bits_per_byte;

    // padding the input
    let mut padded_input: Vec<u8> = big_input.to_vec();
    let input_len = padded_input.len();

    // append the bit "1" followed by zeros
    padded_input.push(0b1000_0000);

    let remainder_count = ((input_len + 1) % num_bytes_per_chunk) % max_actual_bytes_in_last_chunk;

    // pad by zeros until we reach max_actual_bytes_in_last_chunk in the last chunk
    padded_input.append(&mut vec![
        0 as u8;
        max_actual_bytes_in_last_chunk - remainder_count
    ]);

    // append the bit length of the original big_input in little endian
    let mut len_bytes = (input_len as u64 * bits_per_byte as u64)
        .to_le_bytes()
        .to_vec();
    padded_input.append(&mut len_bytes);

    // initial state
    let mut a0 = 0x67452301_u32;
    let mut b0 = 0xefcdab89_u32;
    let mut c0 = 0x98badcfe_u32;
    let mut d0 = 0x10325476_u32;

    // md5_s specifies the per-round shift amounts
    let md5_s: [u32; 64] = [
        7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5,
        9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10,
        15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
    ];

    // integer part of 2^32 * abs(sin([1..64]))
    let md5_k: [u32; 64] = [
        0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613,
        0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193,
        0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d,
        0x2441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
        0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122,
        0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa,
        0xd4ef3085, 0x4881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244,
        0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
        0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb,
        0xeb86d391,
    ];

    let padded_input_len = padded_input.len();
    let num_slices = padded_input_len / num_bytes_per_chunk;

    for j in 0..num_slices {
        // Slicing the padded input into 512bit chunks
        let chunk_bytes = &padded_input[num_bytes_per_chunk * j..num_bytes_per_chunk * (j + 1)];

        // chunk_words is the array of 32bit words converted from the chunk_bytes
        let mut chunk_words: [u32; 16] = [0; 16];
        for g in 0..chunk_words.len() {
            chunk_words[g] = u32::from_le_bytes([
                chunk_bytes[g * 4],
                chunk_bytes[g * 4 + 1],
                chunk_bytes[g * 4 + 2],
                chunk_bytes[g * 4 + 3],
            ]);
        }

        // initializing variables used for the rounds of calculation
        let mut a = a0;
        let mut b = b0;
        let mut c = c0;
        let mut d = d0;
        let mut r: u32;
        let mut g;

        for i in 0..64 {
            if i <= 15 {
                r = md5_f(b, c, d);
                g = i;
            } else if i <= 31 {
                r = md5_g(b, c, d);
                g = (5 * i + 1) % 16;
            } else if i <= 47 {
                r = md5_h(b, c, d);
                g = (3 * i + 5) % 16;
            } else {
                r = md5_i(b, c, d);
                g = (7 * i) % 16;
            }
            // println!("{} {:02x} {:02x} {:02x} {:02x} {:02x}", i, a, b, c, d, r);
            r = r
                .wrapping_add(a)
                .wrapping_add(md5_k[i])
                .wrapping_add(chunk_words[g]);
            a = d;
            d = c;
            c = b;
            b = b.wrapping_add(r.rotate_left(md5_s[i]));
        }

        // updating the state after each chunk is processed
        a0 = a0.wrapping_add(a);
        b0 = b0.wrapping_add(b);
        c0 = c0.wrapping_add(c);
        d0 = d0.wrapping_add(d);
    }

    let mut result = [0_u8; 16];
    result[..4].copy_from_slice(&a0.to_le_bytes());
    result[4..8].copy_from_slice(&b0.to_le_bytes());
    result[8..12].copy_from_slice(&c0.to_le_bytes());
    result[12..].copy_from_slice(&d0.to_le_bytes());

    return result;
}
```