Skip to content

Commit 50749a5

Browse files
lukecotterisomorphic-git-bot
authored andcommitted
fix: improve hashblob performance up to 100% faster (#2144)
* refactor: directly return boolean expression * perf: avoid creating new Uint8Array if it already is * perf: avoid wrapping result in Uint8Array `hashObject` already returns a `Uint8Array` * perf: only create one buffer instead of three * refactor: reintroduce explicit encoding
1 parent cda163f commit 50749a5

File tree

5 files changed

+77
-21
lines changed

5 files changed

+77
-21
lines changed

js/isomorphic-git/index.cjs

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ async function testSubtleSHA1() {
644644
// some browsers that have crypto.subtle.digest don't actually implement SHA-1.
645645
try {
646646
const hash = await subtleSHA1(new Uint8Array([]));
647-
if (hash === 'da39a3ee5e6b4b0d3255bfef95601890afd80709') return true
647+
return hash === 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
648648
} catch (_) {
649649
// no bother
650650
}
@@ -2301,14 +2301,41 @@ class GitTree {
23012301
}
23022302
}
23032303

2304+
/**
2305+
* Represents a Git object and provides methods to wrap and unwrap Git objects
2306+
* according to the Git object format.
2307+
*/
23042308
class GitObject {
2309+
/**
2310+
* Wraps a raw object with a Git header.
2311+
*
2312+
* @param {Object} params - The parameters for wrapping.
2313+
* @param {string} params.type - The type of the Git object (e.g., 'blob', 'tree', 'commit').
2314+
* @param {Uint8Array} params.object - The raw object data to wrap.
2315+
* @returns {Uint8Array} The wrapped Git object as a single buffer.
2316+
*/
23052317
static wrap({ type, object }) {
2306-
return Buffer.concat([
2307-
Buffer.from(`${type} ${object.byteLength.toString()}\x00`),
2308-
Buffer.from(object),
2309-
])
2318+
const header = `${type} ${object.length}\x00`;
2319+
const headerLen = header.length;
2320+
const totalLength = headerLen + object.length;
2321+
2322+
// Allocate a single buffer for the header and object, rather than create multiple buffers
2323+
const wrappedObject = new Uint8Array(totalLength);
2324+
for (let i = 0; i < headerLen; i++) {
2325+
wrappedObject[i] = header.charCodeAt(i);
2326+
}
2327+
wrappedObject.set(object, headerLen);
2328+
2329+
return wrappedObject
23102330
}
23112331

2332+
/**
2333+
* Unwraps a Git object buffer into its type and raw object data.
2334+
*
2335+
* @param {Buffer|Uint8Array} buffer - The buffer containing the wrapped Git object.
2336+
* @returns {{ type: string, object: Buffer }} An object containing the type and the raw object data.
2337+
* @throws {InternalError} If the length specified in the header does not match the actual object length.
2338+
*/
23122339
static unwrap(buffer) {
23132340
const s = buffer.indexOf(32); // first space
23142341
const i = buffer.indexOf(0); // first null value
@@ -10752,17 +10779,18 @@ async function hashBlob({ object }) {
1075210779
// Convert object to buffer
1075310780
if (typeof object === 'string') {
1075410781
object = Buffer.from(object, 'utf8');
10755-
} else {
10756-
object = Buffer.from(object);
10782+
} else if (!(object instanceof Uint8Array)) {
10783+
object = new Uint8Array(object);
1075710784
}
1075810785

1075910786
const type = 'blob';
1076010787
const { oid, object: _object } = await hashObject({
10761-
type: 'blob',
10788+
type,
1076210789
format: 'content',
1076310790
object,
1076410791
});
10765-
return { oid, type, object: new Uint8Array(_object), format: 'wrapped' }
10792+
10793+
return { oid, type, object: _object, format: 'wrapped' }
1076610794
} catch (err) {
1076710795
err.caller = 'git.hashBlob';
1076810796
throw err

js/isomorphic-git/index.js

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,7 @@ async function testSubtleSHA1() {
638638
// some browsers that have crypto.subtle.digest don't actually implement SHA-1.
639639
try {
640640
const hash = await subtleSHA1(new Uint8Array([]));
641-
if (hash === 'da39a3ee5e6b4b0d3255bfef95601890afd80709') return true
641+
return hash === 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
642642
} catch (_) {
643643
// no bother
644644
}
@@ -2295,14 +2295,41 @@ class GitTree {
22952295
}
22962296
}
22972297

2298+
/**
2299+
* Represents a Git object and provides methods to wrap and unwrap Git objects
2300+
* according to the Git object format.
2301+
*/
22982302
class GitObject {
2303+
/**
2304+
* Wraps a raw object with a Git header.
2305+
*
2306+
* @param {Object} params - The parameters for wrapping.
2307+
* @param {string} params.type - The type of the Git object (e.g., 'blob', 'tree', 'commit').
2308+
* @param {Uint8Array} params.object - The raw object data to wrap.
2309+
* @returns {Uint8Array} The wrapped Git object as a single buffer.
2310+
*/
22992311
static wrap({ type, object }) {
2300-
return Buffer.concat([
2301-
Buffer.from(`${type} ${object.byteLength.toString()}\x00`),
2302-
Buffer.from(object),
2303-
])
2312+
const header = `${type} ${object.length}\x00`;
2313+
const headerLen = header.length;
2314+
const totalLength = headerLen + object.length;
2315+
2316+
// Allocate a single buffer for the header and object, rather than create multiple buffers
2317+
const wrappedObject = new Uint8Array(totalLength);
2318+
for (let i = 0; i < headerLen; i++) {
2319+
wrappedObject[i] = header.charCodeAt(i);
2320+
}
2321+
wrappedObject.set(object, headerLen);
2322+
2323+
return wrappedObject
23042324
}
23052325

2326+
/**
2327+
* Unwraps a Git object buffer into its type and raw object data.
2328+
*
2329+
* @param {Buffer|Uint8Array} buffer - The buffer containing the wrapped Git object.
2330+
* @returns {{ type: string, object: Buffer }} An object containing the type and the raw object data.
2331+
* @throws {InternalError} If the length specified in the header does not match the actual object length.
2332+
*/
23062333
static unwrap(buffer) {
23072334
const s = buffer.indexOf(32); // first space
23082335
const i = buffer.indexOf(0); // first null value
@@ -10746,17 +10773,18 @@ async function hashBlob({ object }) {
1074610773
// Convert object to buffer
1074710774
if (typeof object === 'string') {
1074810775
object = Buffer.from(object, 'utf8');
10749-
} else {
10750-
object = Buffer.from(object);
10776+
} else if (!(object instanceof Uint8Array)) {
10777+
object = new Uint8Array(object);
1075110778
}
1075210779

1075310780
const type = 'blob';
1075410781
const { oid, object: _object } = await hashObject({
10755-
type: 'blob',
10782+
type,
1075610783
format: 'content',
1075710784
object,
1075810785
});
10759-
return { oid, type, object: new Uint8Array(_object), format: 'wrapped' }
10786+
10787+
return { oid, type, object: _object, format: 'wrapped' }
1076010788
} catch (err) {
1076110789
err.caller = 'git.hashBlob';
1076210790
throw err

js/isomorphic-git/index.umd.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/isomorphic-git/index.umd.min.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/isomorphic-git/size_report.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)