@@ -9,7 +9,8 @@ use std::sync::Mutex;
9
9
use std:: thread;
10
10
11
11
/// Atomics can be safely shared between threads.
12
- struct Shared {
12
+ struct Shared < ' a > {
13
+ input : & ' a str ,
13
14
done : AtomicBool ,
14
15
counter : AtomicI32 ,
15
16
}
@@ -27,135 +28,174 @@ pub fn parse(input: &str) -> &str {
27
28
28
29
/// Hash each key once.
29
30
pub fn part1 ( input : & str ) -> i32 {
30
- let md5 = |n| {
31
- let ( mut buffer, size) = format_string ( input, n) ;
32
- hash ( & mut buffer, size)
33
- } ;
34
- generate_pad ( md5)
31
+ generate_pad ( input, false )
35
32
}
36
33
37
34
/// Hash each key an additional 2016 times.
38
35
pub fn part2 ( input : & str ) -> i32 {
39
- let md5 = |n| {
40
- let ( mut buffer, size) = format_string ( input, n) ;
41
- let mut result = hash ( & mut buffer, size) ;
42
-
43
- for _ in 0 ..2016 {
44
- buffer[ 0 ..8 ] . copy_from_slice ( & to_ascii ( result. 0 ) ) ;
45
- buffer[ 8 ..16 ] . copy_from_slice ( & to_ascii ( result. 1 ) ) ;
46
- buffer[ 16 ..24 ] . copy_from_slice ( & to_ascii ( result. 2 ) ) ;
47
- buffer[ 24 ..32 ] . copy_from_slice ( & to_ascii ( result. 3 ) ) ;
48
- result = hash ( & mut buffer, 32 ) ;
49
- }
50
-
51
- result
52
- } ;
53
- generate_pad ( md5)
54
- }
55
-
56
- fn format_string ( prefix : & str , n : i32 ) -> ( [ u8 ; 64 ] , usize ) {
57
- let string = format ! ( "{prefix}{n}" ) ;
58
- let size = string. len ( ) ;
59
-
60
- let mut buffer = [ 0 ; 64 ] ;
61
- buffer[ 0 ..size] . copy_from_slice ( string. as_bytes ( ) ) ;
62
-
63
- ( buffer, size)
36
+ generate_pad ( input, true )
64
37
}
65
38
66
39
/// Find the first 64 keys that sastify the rules.
67
- fn generate_pad ( md5 : impl Fn ( i32 ) -> ( u32 , u32 , u32 , u32 ) + Copy + Sync ) -> i32 {
68
- let shared = Shared { done : AtomicBool :: new ( false ) , counter : AtomicI32 :: new ( 0 ) } ;
40
+ fn generate_pad ( input : & str , part_two : bool ) -> i32 {
41
+ let shared = Shared { input , done : AtomicBool :: new ( false ) , counter : AtomicI32 :: new ( 0 ) } ;
69
42
let exclusive =
70
43
Exclusive { threes : BTreeMap :: new ( ) , fives : BTreeMap :: new ( ) , found : BTreeSet :: new ( ) } ;
71
44
let mutex = Mutex :: new ( exclusive) ;
72
45
73
46
// Use as many cores as possible to parallelize the search.
74
47
thread:: scope ( |scope| {
75
48
for _ in 0 ..thread:: available_parallelism ( ) . unwrap ( ) . get ( ) {
76
- scope. spawn ( || check_keys ( & shared, & mutex, md5 ) ) ;
49
+ scope. spawn ( || worker ( & shared, & mutex, part_two ) ) ;
77
50
}
78
51
} ) ;
79
52
80
53
let exclusive = mutex. into_inner ( ) . unwrap ( ) ;
81
54
* exclusive. found . iter ( ) . nth ( 63 ) . unwrap ( )
82
55
}
83
56
84
- fn check_keys (
85
- shared : & Shared ,
86
- mutex : & Mutex < Exclusive > ,
87
- md5 : impl Fn ( i32 ) -> ( u32 , u32 , u32 , u32 ) ,
88
- ) {
57
+ #[ cfg( not( feature = "simd" ) ) ]
58
+ fn worker ( shared : & Shared < ' _ > , mutex : & Mutex < Exclusive > , part_two : bool ) {
89
59
while !shared. done . load ( Ordering :: Relaxed ) {
90
60
// Get the next key to check.
91
61
let n = shared. counter . fetch_add ( 1 , Ordering :: Relaxed ) ;
62
+
92
63
// Calculate the hash.
93
- let ( a, b, c, d) = md5 ( n) ;
94
-
95
- // Check for sequences of 3 or 5 consecutive matching digits.
96
- let mut prev = u32:: MAX ;
97
- let mut same = 1 ;
98
- let mut three = 0 ;
99
- let mut five = 0 ;
100
-
101
- for mut word in [ d, c, b, a] {
102
- for _ in 0 ..8 {
103
- let next = word & 0xf ;
104
-
105
- if next == prev {
106
- same += 1 ;
107
- } else {
108
- same = 1 ;
109
- }
64
+ let ( mut buffer, size) = format_string ( shared. input , n) ;
65
+ let mut result = hash ( & mut buffer, size) ;
110
66
111
- if same == 3 {
112
- three = 1 << next;
113
- }
114
- if same == 5 {
115
- five |= 1 << next;
67
+ if part_two {
68
+ for _ in 0 ..2016 {
69
+ buffer[ 0 ..8 ] . copy_from_slice ( & to_ascii ( result. 0 ) ) ;
70
+ buffer[ 8 ..16 ] . copy_from_slice ( & to_ascii ( result. 1 ) ) ;
71
+ buffer[ 16 ..24 ] . copy_from_slice ( & to_ascii ( result. 2 ) ) ;
72
+ buffer[ 24 ..32 ] . copy_from_slice ( & to_ascii ( result. 3 ) ) ;
73
+ result = hash ( & mut buffer, 32 ) ;
74
+ }
75
+ }
76
+
77
+ check ( shared, mutex, n, result) ;
78
+ }
79
+ }
80
+
81
+ /// Use SIMD to compute hashes in parallel in blocks of 32.
82
+ #[ cfg( feature = "simd" ) ]
83
+ #[ allow( clippy:: needless_range_loop) ]
84
+ fn worker ( shared : & Shared < ' _ > , mutex : & Mutex < Exclusive > , part_two : bool ) {
85
+ let mut result = ( [ 0 ; 32 ] , [ 0 ; 32 ] , [ 0 ; 32 ] , [ 0 ; 32 ] ) ;
86
+ let mut buffers = [ [ 0 ; 64 ] ; 32 ] ;
87
+
88
+ while !shared. done . load ( Ordering :: Relaxed ) {
89
+ // Get the next key to check.
90
+ let start = shared. counter . fetch_add ( 32 , Ordering :: Relaxed ) ;
91
+
92
+ // Calculate the hash.
93
+ for i in 0 ..32 {
94
+ let ( mut buffer, size) = format_string ( shared. input , start + i as i32 ) ;
95
+ let ( a, b, c, d) = hash ( & mut buffer, size) ;
96
+
97
+ result. 0 [ i] = a;
98
+ result. 1 [ i] = b;
99
+ result. 2 [ i] = c;
100
+ result. 3 [ i] = d;
101
+ }
102
+
103
+ if part_two {
104
+ for _ in 0 ..2016 {
105
+ for i in 0 ..32 {
106
+ buffers[ i] [ 0 ..8 ] . copy_from_slice ( & to_ascii ( result. 0 [ i] ) ) ;
107
+ buffers[ i] [ 8 ..16 ] . copy_from_slice ( & to_ascii ( result. 1 [ i] ) ) ;
108
+ buffers[ i] [ 16 ..24 ] . copy_from_slice ( & to_ascii ( result. 2 [ i] ) ) ;
109
+ buffers[ i] [ 24 ..32 ] . copy_from_slice ( & to_ascii ( result. 3 [ i] ) ) ;
116
110
}
111
+ result = simd:: hash :: < 32 > ( & mut buffers, 32 ) ;
112
+ }
113
+ }
114
+
115
+ for i in 0 ..32 {
116
+ let hash = ( result. 0 [ i] , result. 1 [ i] , result. 2 [ i] , result. 3 [ i] ) ;
117
+ check ( shared, mutex, start + i as i32 , hash) ;
118
+ }
119
+ }
120
+ }
117
121
118
- word >>= 4 ;
119
- prev = next;
122
+ /// Check for sequences of 3 or 5 consecutive matching digits.
123
+ fn check ( shared : & Shared < ' _ > , mutex : & Mutex < Exclusive > , n : i32 , hash : ( u32 , u32 , u32 , u32 ) ) {
124
+ let ( a, b, c, d) = hash;
125
+
126
+ let mut prev = u32:: MAX ;
127
+ let mut same = 1 ;
128
+ let mut three = 0 ;
129
+ let mut five = 0 ;
130
+
131
+ for mut word in [ d, c, b, a] {
132
+ for _ in 0 ..8 {
133
+ let next = word & 0xf ;
134
+
135
+ if next == prev {
136
+ same += 1 ;
137
+ } else {
138
+ same = 1 ;
120
139
}
140
+
141
+ if same == 3 {
142
+ three = 1 << next;
143
+ }
144
+ if same == 5 {
145
+ five |= 1 << next;
146
+ }
147
+
148
+ word >>= 4 ;
149
+ prev = next;
121
150
}
151
+ }
122
152
123
- if three != 0 || five != 0 {
124
- let mut exclusive = mutex. lock ( ) . unwrap ( ) ;
125
- let mut candidates = Vec :: new ( ) ;
153
+ if three != 0 || five != 0 {
154
+ let mut exclusive = mutex. lock ( ) . unwrap ( ) ;
155
+ let mut candidates = Vec :: new ( ) ;
126
156
127
- // Compare against all 5 digit sequences.
128
- if three != 0 {
129
- exclusive. threes . insert ( n, three) ;
157
+ // Compare against all 5 digit sequences.
158
+ if three != 0 {
159
+ exclusive. threes . insert ( n, three) ;
130
160
131
- for ( & index, & mask) in exclusive. fives . range ( n + 1 ..n + 1000 ) {
132
- if three & mask != 0 {
133
- candidates. push ( index) ;
134
- }
161
+ for ( _, mask) in exclusive. fives . range ( n + 1 ..n + 1001 ) {
162
+ if three & mask != 0 {
163
+ candidates. push ( n) ;
135
164
}
136
165
}
166
+ }
137
167
138
- // Compare against all 3 digit sequences.
139
- if five != 0 {
140
- exclusive. fives . insert ( n, five) ;
168
+ // Compare against all 3 digit sequences.
169
+ if five != 0 {
170
+ exclusive. fives . insert ( n, five) ;
141
171
142
- for ( & index, & mask) in exclusive. threes . range ( n - 1000 ..n - 1 ) {
143
- if five & mask != 0 {
144
- candidates. push ( index) ;
145
- }
172
+ for ( & index, & mask) in exclusive. threes . range ( n - 1000 ..n) {
173
+ if five & mask != 0 {
174
+ candidates. push ( index) ;
146
175
}
147
176
}
177
+ }
148
178
149
- // Add any matching keys found, finishing once we have at least 64 keys.
150
- exclusive. found . extend ( candidates) ;
179
+ // Add any matching keys found, finishing once we have at least 64 keys.
180
+ exclusive. found . extend ( candidates) ;
151
181
152
- if exclusive. found . len ( ) >= 64 {
153
- shared. done . store ( true , Ordering :: Relaxed ) ;
154
- }
182
+ if exclusive. found . len ( ) >= 64 {
183
+ shared. done . store ( true , Ordering :: Relaxed ) ;
155
184
}
156
185
}
157
186
}
158
187
188
+ /// Write the salt and integer index as ASCII characters.
189
+ fn format_string ( prefix : & str , n : i32 ) -> ( [ u8 ; 64 ] , usize ) {
190
+ let string = format ! ( "{prefix}{n}" ) ;
191
+ let size = string. len ( ) ;
192
+
193
+ let mut buffer = [ 0 ; 64 ] ;
194
+ buffer[ 0 ..size] . copy_from_slice ( string. as_bytes ( ) ) ;
195
+
196
+ ( buffer, size)
197
+ }
198
+
159
199
/// Quickly convert a `u32` to an array of 8 ASCII values.
160
200
fn to_ascii ( n : u32 ) -> [ u8 ; 8 ] {
161
201
// Spread each nibble into its own byte, for example `1234abcd` becomes `010203040a0b0c0d`.
0 commit comments