matrix_boost 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +16 -0
- data/README.md +19 -6
- data/Rakefile +49 -0
- data/ext/extension.c +23 -5
- data/ext/matrices.c +115 -12
- data/ext/matrices.h +10 -5
- data/lib/matrix_boost.rb +5 -14
- data/lib/matrix_boost/core_extensions.rb +9 -0
- data/lib/matrix_boost/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: edf1ea7cf2375607d1680ad931b9da4740bd94ed4751bb11076c7f76f8eb3466
|
4
|
+
data.tar.gz: 8b8fde75f0be426b15b1fb44493bc5cb8373985a718edb429686d558d7e63535
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b40d23ee4416f3f411cf9e68f597f468cfd8f60dec26a32053b108e60edb3be7a1c338cf7afb9a52fdc32e36b68798d39410daf7e99c746eb800d000cab0b7f1
|
7
|
+
data.tar.gz: f287eabc86984559b619340f54a6ac044223b680e86502ba4b3cde448c755dcd00ae2965fc158851f91d7f410e6e8c5860127048667ea72ce7b49e116adb4f2d
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v0.2.0](https://github.com/Bajena/matrix_boost/tree/v0.2.0) (2020-04-18)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/Bajena/matrix_boost/compare/v0.1.1...v0.2.0)
|
6
|
+
|
7
|
+
**Closed issues:**
|
8
|
+
|
9
|
+
- Add inversion boost [\#1](https://github.com/Bajena/matrix_boost/issues/1)
|
10
|
+
|
11
|
+
**Merged pull requests:**
|
12
|
+
|
13
|
+
- Add inversion [\#2](https://github.com/Bajena/matrix_boost/pull/2) ([Bajena](https://github.com/Bajena))
|
14
|
+
|
15
|
+
## [v0.1.1](https://github.com/Bajena/matrix_boost/tree/v0.1.1) (2020-04-12)
|
16
|
+
|
17
|
+
[Full Changelog](https://github.com/Bajena/matrix_boost/compare/v0.1.0...v0.1.1)
|
18
|
+
|
3
19
|
## [v0.1.0](https://github.com/Bajena/matrix_boost/tree/v0.1.0) (2020-04-12)
|
4
20
|
|
5
21
|
[Full Changelog](https://github.com/Bajena/matrix_boost/compare/8f26eddc7e648c0c68375b52b736535126bfd47f...v0.1.0)
|
data/README.md
CHANGED
@@ -8,21 +8,34 @@ from Ruby's [https://ruby-doc.org/stdlib-2.5.1/libdoc/matrix/rdoc/Matrix.html](M
|
|
8
8
|
### Performance comparison
|
9
9
|
|
10
10
|
```bash
|
11
|
-
|
11
|
+
→ bin/rake benchmark_multiply; bin/rake benchmark_inverse
|
12
|
+
Benchmark multiplication (dim = 4, n = 1000000)...
|
12
13
|
user system total real
|
13
|
-
Ruby matrix multiply:
|
14
|
-
C matrix multiply:
|
15
|
-
Ruby matrix multiply after monkey patch:
|
14
|
+
Ruby matrix multiply: 22.627594 0.180447 22.808041 ( 23.473300)
|
15
|
+
C matrix multiply: 12.393963 0.124988 12.518951 ( 13.102763)
|
16
|
+
Ruby matrix multiply after monkey patch: 11.493074 0.073740 11.566814 ( 11.645836)
|
17
|
+
>Ruby slower (%): 1.825695 1.443715 NaN ( 1.791477)
|
18
|
+
|
19
|
+
Benchmark inversion (dim = 4, n = 1000000)...
|
20
|
+
user system total real
|
21
|
+
Ruby matrix inverse: 26.191386 0.067096 26.258482 ( 26.328944)
|
22
|
+
C matrix inverse: 4.911581 0.005300 4.916881 ( 4.920316)
|
23
|
+
Ruby matrix inverse after monkey patch: 5.180995 0.003944 5.184939 ( 5.187873)
|
24
|
+
>Ruby slower (%): 5.332577 12.659623 NaN ( 5.351068)
|
16
25
|
```
|
17
26
|
|
18
|
-
|
27
|
+
- Ruby's multiplication of 4-dimensional martices is **~79%** slower than the same operation
|
28
|
+
implemented in a C extension 🎉.
|
29
|
+
- Ruby's inversion of 4-dimensional matrices is **~435%** slower than the same operation
|
19
30
|
implemented in a C extension 🎉.
|
20
31
|
|
21
32
|
### Matrix class core extension
|
22
33
|
Even though this gem was created mainly for learning how to use C extensions in Ruby
|
23
34
|
you should still be able to use it in your production code.
|
24
35
|
|
25
|
-
You can
|
36
|
+
You can install the gem by adding `gem "matrix_boost"` to your gemfile.
|
37
|
+
|
38
|
+
Then you can either use `MatrixBoost.multiply(m1, m2)` or `MatrixBoost.invert(m)` or replace the original methods
|
26
39
|
from `Matrix` by calling `MatrixBoost.apply_core_extensions`.
|
27
40
|
|
28
41
|
### How do I play around?
|
data/Rakefile
CHANGED
@@ -17,6 +17,55 @@ task :compile do
|
|
17
17
|
puts "Done"
|
18
18
|
end
|
19
19
|
|
20
|
+
task :benchmark_multiply do
|
21
|
+
require "matrix_boost"
|
22
|
+
|
23
|
+
dim = 4
|
24
|
+
n = 10000
|
25
|
+
|
26
|
+
m1 = Matrix.build(dim) { rand }
|
27
|
+
m2 = Matrix.build(dim) { rand }
|
28
|
+
|
29
|
+
puts "Benchmark multiplication (dim = #{dim}, n = #{n})..."
|
30
|
+
|
31
|
+
Benchmark.benchmark(Benchmark::CAPTION, 45, Benchmark::FORMAT, ">Ruby slower (%):") do |x|
|
32
|
+
r = x.report("Ruby matrix multiply:") { n.times { m1 * m2 } }
|
33
|
+
c = x.report("C matrix multiply:") { n.times { MatrixBoost.multiply(m1, m2) } }
|
34
|
+
|
35
|
+
MatrixBoost.apply_core_extensions
|
36
|
+
x.report("Ruby matrix multiply after monkey patch:") { n.times { m1 * m2 } }
|
37
|
+
|
38
|
+
[r / c]
|
39
|
+
end
|
40
|
+
|
41
|
+
puts "Done"
|
42
|
+
puts ""
|
43
|
+
end
|
44
|
+
|
45
|
+
task :benchmark_inverse do
|
46
|
+
require "matrix_boost"
|
47
|
+
|
48
|
+
dim = 4
|
49
|
+
n = 10000
|
50
|
+
|
51
|
+
puts "Benchmark inversion (dim = #{dim}, n = #{n})..."
|
52
|
+
|
53
|
+
m = Matrix.build(dim) { rand }
|
54
|
+
|
55
|
+
Benchmark.benchmark(Benchmark::CAPTION, 45, Benchmark::FORMAT, ">Ruby slower (%):") do |x|
|
56
|
+
r = x.report("Ruby matrix inverse:") { n.times { m.inverse } }
|
57
|
+
c = x.report("C matrix inverse:") { n.times { MatrixBoost.invert(m) } }
|
58
|
+
|
59
|
+
MatrixBoost.apply_core_extensions
|
60
|
+
x.report("Ruby matrix inverse after monkey patch:") { n.times { m.inverse } }
|
61
|
+
|
62
|
+
[r / c]
|
63
|
+
end
|
64
|
+
|
65
|
+
puts "Done"
|
66
|
+
puts ""
|
67
|
+
end
|
68
|
+
|
20
69
|
GitHubChangelogGenerator::RakeTask.new :changelog do |config|
|
21
70
|
config.user = "Bajena"
|
22
71
|
config.project = "matrix_boost"
|
data/ext/extension.c
CHANGED
@@ -29,7 +29,7 @@ Matrix* rb_array_to_matrix(VALUE a) {
|
|
29
29
|
Check_Type(first_row, T_ARRAY);
|
30
30
|
long columns = RARRAY_LEN(first_row);
|
31
31
|
|
32
|
-
Matrix *matrix =
|
32
|
+
Matrix *matrix = matrix_constructor(rows, columns);
|
33
33
|
int row;
|
34
34
|
int column;
|
35
35
|
|
@@ -51,14 +51,31 @@ static VALUE mul_matrix(VALUE self, VALUE m1, VALUE m2) {
|
|
51
51
|
Matrix *m1c = rb_array_to_matrix(m1);
|
52
52
|
Matrix *m2c = rb_array_to_matrix(m2);
|
53
53
|
|
54
|
-
Matrix *multiplied =
|
54
|
+
Matrix *multiplied = matrix_multiply(m1c, m2c);
|
55
55
|
|
56
|
-
|
57
|
-
|
56
|
+
matrix_destroy(m1c);
|
57
|
+
matrix_destroy(m2c);
|
58
58
|
|
59
59
|
if (multiplied) {
|
60
60
|
VALUE result = matrix_to_rb_array(multiplied);
|
61
|
-
|
61
|
+
matrix_destroy(multiplied);
|
62
|
+
return result;
|
63
|
+
} else {
|
64
|
+
return Qnil;
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
static VALUE inv_matrix(VALUE self, VALUE m) {
|
69
|
+
Check_Type(m, T_ARRAY);
|
70
|
+
|
71
|
+
Matrix *mc = rb_array_to_matrix(m);
|
72
|
+
Matrix *inverted = matrix_invert(mc);
|
73
|
+
|
74
|
+
matrix_destroy(mc);
|
75
|
+
|
76
|
+
if (inverted) {
|
77
|
+
VALUE result = matrix_to_rb_array(inverted);
|
78
|
+
matrix_destroy(inverted);
|
62
79
|
return result;
|
63
80
|
} else {
|
64
81
|
return Qnil;
|
@@ -69,4 +86,5 @@ void Init_extension(void) {
|
|
69
86
|
VALUE MatrixBoost = rb_define_module("MatrixBoost");
|
70
87
|
VALUE NativeHelpers = rb_define_class_under(MatrixBoost, "NativeHelpers", rb_cObject);
|
71
88
|
rb_define_singleton_method(NativeHelpers, "mul_matrix", mul_matrix, 2);
|
89
|
+
rb_define_singleton_method(NativeHelpers, "inv_matrix", inv_matrix, 1);
|
72
90
|
}
|
data/ext/matrices.c
CHANGED
@@ -21,10 +21,11 @@ rows |
|
|
21
21
|
the matrix is an array of array pointers where each array pointer corresponds to a vector
|
22
22
|
*/
|
23
23
|
|
24
|
-
static double
|
24
|
+
static double matrix_vector_multiply(double *col, double *row, int length);
|
25
|
+
static int matrix_row_scalar_multiply(Matrix *m, int row, float factor);
|
25
26
|
|
26
27
|
/* make a zero matrix of given dimensions */
|
27
|
-
Matrix *
|
28
|
+
Matrix *matrix_constructor(int r, int c){
|
28
29
|
unsigned int i;
|
29
30
|
Matrix *m;
|
30
31
|
if(r <= 0 || c <= 0){
|
@@ -41,7 +42,7 @@ Matrix *constructor(int r, int c){
|
|
41
42
|
}
|
42
43
|
|
43
44
|
/* free memory associated with the matrix */
|
44
|
-
int
|
45
|
+
int matrix_destroy(Matrix *m){
|
45
46
|
unsigned int i;
|
46
47
|
if(m == NULL)
|
47
48
|
return FAIL;
|
@@ -52,8 +53,19 @@ int destroy_matrix(Matrix *m){
|
|
52
53
|
return SUCC;
|
53
54
|
}
|
54
55
|
|
56
|
+
/* enter 1s along the main diagonal */
|
57
|
+
Matrix *matrix_identity(int length){
|
58
|
+
unsigned int i, j;
|
59
|
+
Matrix *m;
|
60
|
+
m = matrix_constructor(length, length);
|
61
|
+
for(i = 0; i < length; i++){
|
62
|
+
j = i;
|
63
|
+
(m->numbers[i])[j] = 1;
|
64
|
+
}
|
65
|
+
return m;
|
66
|
+
}
|
55
67
|
/* print the matrix */
|
56
|
-
int
|
68
|
+
int matrix_print(Matrix *m){
|
57
69
|
unsigned int i, j;
|
58
70
|
if(m == NULL)
|
59
71
|
return FAIL;
|
@@ -66,12 +78,12 @@ int print(Matrix *m){
|
|
66
78
|
return SUCC;
|
67
79
|
}
|
68
80
|
|
69
|
-
Matrix *
|
81
|
+
Matrix *matrix_transpose(Matrix *m){
|
70
82
|
Matrix *trans;
|
71
83
|
unsigned int i, j;
|
72
84
|
if(m == NULL)
|
73
85
|
return NULL;
|
74
|
-
trans =
|
86
|
+
trans = matrix_constructor(m->columns, m->rows);
|
75
87
|
for(i = 0; i < trans->columns; i++){
|
76
88
|
for(j = 0; j < trans->rows; j++)
|
77
89
|
trans->numbers[i][j] = m->numbers[j][i];
|
@@ -80,26 +92,117 @@ Matrix *transpose(Matrix *m){
|
|
80
92
|
}
|
81
93
|
|
82
94
|
/* m1 x m2 */
|
83
|
-
Matrix *
|
95
|
+
Matrix *matrix_multiply(Matrix *m1, Matrix *m2){
|
84
96
|
Matrix *product, *trans;
|
85
97
|
unsigned int i, j;
|
86
98
|
if(m1 == NULL || m2 == NULL)
|
87
99
|
return NULL;
|
88
100
|
if(m1->columns != m2->rows)
|
89
101
|
return NULL;
|
90
|
-
trans =
|
91
|
-
product =
|
102
|
+
trans = matrix_transpose(m1);
|
103
|
+
product = matrix_constructor(m1->rows, m2->columns);
|
92
104
|
for(i = 0; i < product->columns; i++){
|
93
105
|
for(j = 0; j < product->rows; j++){
|
94
|
-
product->numbers[i][j] =
|
106
|
+
product->numbers[i][j] = matrix_vector_multiply(trans->numbers[j], m2->numbers[i], m2->rows);
|
95
107
|
}
|
96
108
|
}
|
97
|
-
|
109
|
+
matrix_destroy(trans);
|
98
110
|
return product;
|
99
111
|
}
|
100
112
|
|
113
|
+
/* matrix m will become the matrix_identity so the caller must save their matrix themselves */
|
114
|
+
Matrix *matrix_invert(Matrix *m){
|
115
|
+
Matrix *invert;
|
116
|
+
unsigned int i, j, l;
|
117
|
+
double factor;
|
118
|
+
if(m == NULL)
|
119
|
+
return NULL;
|
120
|
+
if((m)->columns != (m)->rows)
|
121
|
+
return NULL;
|
122
|
+
invert = matrix_identity((m)->rows);
|
123
|
+
|
124
|
+
/* matrix_reduce each of the rows to get a lower triangle */
|
125
|
+
for(i = 0; i < (m)->columns; i++){
|
126
|
+
for(j = i + 1; j < (m)->rows; j++){
|
127
|
+
if((m)->numbers[i][i] == 0){
|
128
|
+
for(l=i+1; l < m->rows; l++){
|
129
|
+
if(m->numbers[l][l] != 0){
|
130
|
+
matrix_row_swap(m, i, l);
|
131
|
+
break;
|
132
|
+
}
|
133
|
+
}
|
134
|
+
continue;
|
135
|
+
}
|
136
|
+
factor = (m)->numbers[i][j]/((m)->numbers[i][i]);
|
137
|
+
matrix_reduce(invert, i, j, factor);
|
138
|
+
matrix_reduce((m), i, j, factor);
|
139
|
+
}
|
140
|
+
}
|
141
|
+
/* now finish the upper triangle */
|
142
|
+
for(i = (m)->columns - 1; i > 0; i--){
|
143
|
+
for(j = i-1; j>=0; j--){
|
144
|
+
if((m)->numbers[i][i] == 0)
|
145
|
+
continue;
|
146
|
+
if(j == -1)
|
147
|
+
break;
|
148
|
+
factor = (m)->numbers[i][j]/((m)->numbers[i][i]);
|
149
|
+
matrix_reduce(invert, i, j, factor);
|
150
|
+
matrix_reduce((m), i, j, factor);
|
151
|
+
}
|
152
|
+
}
|
153
|
+
/* scale everything to 1 */
|
154
|
+
for(i = 0; i < (m)->columns; i++){
|
155
|
+
if((m)->numbers[i][i]==0)
|
156
|
+
continue;
|
157
|
+
factor = 1/((m)->numbers[i][i]);
|
158
|
+
matrix_row_scalar_multiply(invert, i, factor);
|
159
|
+
matrix_row_scalar_multiply((m), i, factor);
|
160
|
+
}
|
161
|
+
return invert;
|
162
|
+
}
|
163
|
+
|
164
|
+
int matrix_row_swap(Matrix *m, int a, int b){
|
165
|
+
double temp;
|
166
|
+
unsigned int i;
|
167
|
+
if(m == NULL)
|
168
|
+
return FAIL;
|
169
|
+
if(m->rows <= a || m->rows <= b)
|
170
|
+
return FAIL;
|
171
|
+
for(i = 0; i < m->columns; i++){
|
172
|
+
temp = m->numbers[i][a];
|
173
|
+
m->numbers[i][a] = m->numbers[i][b];
|
174
|
+
m->numbers[i][b] = temp;
|
175
|
+
}
|
176
|
+
return SUCC;
|
177
|
+
}
|
178
|
+
|
179
|
+
static int matrix_row_scalar_multiply(Matrix *m, int row, float factor){
|
180
|
+
int i;
|
181
|
+
if(m == NULL)
|
182
|
+
return FAIL;
|
183
|
+
if(m->rows <= row)
|
184
|
+
return FAIL;
|
185
|
+
for(i = 0; i < m->columns; i++)
|
186
|
+
m->numbers[i][row] *= factor;
|
187
|
+
return SUCC;
|
188
|
+
}
|
189
|
+
|
190
|
+
/* matrix_reduce row b by factor*a */
|
191
|
+
int matrix_reduce(Matrix *m, int a, int b, float factor){
|
192
|
+
int i;
|
193
|
+
if(m == NULL)
|
194
|
+
return FAIL;
|
195
|
+
if(m->rows < a || m->rows < b)
|
196
|
+
return FAIL;
|
197
|
+
for(i = 0; i < m->columns; i++){
|
198
|
+
m->numbers[i][b] -= m->numbers[i][a]*factor;
|
199
|
+
}
|
200
|
+
|
201
|
+
return SUCC;
|
202
|
+
}
|
203
|
+
|
101
204
|
/* v1 x v2 -- simply a helper function -- computes dot product between two vectors*/
|
102
|
-
static double
|
205
|
+
static double matrix_vector_multiply(double *col, double *row, int length){
|
103
206
|
double sum;
|
104
207
|
unsigned int i;
|
105
208
|
sum = 0;
|
data/ext/matrices.h
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
#ifdef __cplusplus
|
5
5
|
extern "C" {
|
6
6
|
#endif
|
7
|
+
|
7
8
|
/* current representation of a matrix in my mind */
|
8
9
|
typedef struct Matrix{
|
9
10
|
int rows;
|
@@ -11,11 +12,15 @@ typedef struct Matrix{
|
|
11
12
|
double **numbers;
|
12
13
|
} Matrix;
|
13
14
|
|
14
|
-
Matrix *
|
15
|
-
|
16
|
-
int
|
17
|
-
|
18
|
-
Matrix *
|
15
|
+
Matrix *matrix_constructor(int r, int c);
|
16
|
+
Matrix *matrix_identity(int length);
|
17
|
+
int matrix_destroy(Matrix *m);
|
18
|
+
int matrix_print(Matrix *m);
|
19
|
+
Matrix *matrix_transpose(Matrix *m);
|
20
|
+
Matrix *matrix_multiply(Matrix *m1, Matrix *m2);
|
21
|
+
Matrix *matrix_invert(Matrix *m);
|
22
|
+
int matrix_reduce(Matrix *m, int a, int b, float factor);
|
23
|
+
int matrix_row_swap(Matrix *m, int a, int b);
|
19
24
|
|
20
25
|
#ifdef __cplusplus
|
21
26
|
}
|
data/lib/matrix_boost.rb
CHANGED
@@ -11,26 +11,17 @@ module MatrixBoost
|
|
11
11
|
Matrix[*NativeHelpers.mul_matrix(m1.to_a, m2.to_a)]
|
12
12
|
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
m1 = Matrix.build(dim) { rand }
|
19
|
-
m2 = Matrix.build(dim) { rand }
|
20
|
-
|
21
|
-
Benchmark.benchmark(Benchmark::CAPTION, 45, Benchmark::FORMAT) do |x|
|
22
|
-
x.report("Ruby matrix multiply:") { n.times { m1 * m2 } }
|
23
|
-
x.report("C matrix multiply:") { n.times { multiply(m1, m2) } }
|
24
|
-
|
25
|
-
apply_core_extensions
|
26
|
-
x.report("Ruby matrix multiply after monkey patch:") { n.times { m1 * m2 } }
|
27
|
-
end
|
14
|
+
# @param m [Matrix] Stdlib Matrix instance
|
15
|
+
# @return [Matrix] Inverted matrix
|
16
|
+
def invert(m)
|
17
|
+
Matrix[*NativeHelpers.inv_matrix(m.to_a)]
|
28
18
|
end
|
29
19
|
|
30
20
|
def apply_core_extensions
|
31
21
|
require "matrix_boost/core_extensions"
|
32
22
|
|
33
23
|
Matrix.prepend MatrixBoost::CoreExtensions::Multiply
|
24
|
+
Matrix.prepend MatrixBoost::CoreExtensions::Inverse
|
34
25
|
end
|
35
26
|
end
|
36
27
|
end
|
@@ -15,5 +15,14 @@ module MatrixBoost
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
18
|
+
|
19
|
+
module Inverse
|
20
|
+
# Overrides https://github.com/ruby/matrix/blob/master/lib/matrix.rb#L1175
|
21
|
+
def inverse
|
22
|
+
raise ErrDimensionMismatch unless square?
|
23
|
+
|
24
|
+
MatrixBoost.invert(self)
|
25
|
+
end
|
26
|
+
end
|
18
27
|
end
|
19
28
|
end
|
data/lib/matrix_boost/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: matrix_boost
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jan Bajena
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-04-
|
11
|
+
date: 2020-04-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|