mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 22:13:26 +00:00
streams: Add LazySet for subscribers.
This defers O(N*S) operations, where
N = number of streams
S = number of subscribers per stream
In many cases we never do an O(N) operation on
a stream. Exceptions include:
- checking stream links from the compose box
- editing a stream
- adding members to a newly added stream
An operation that used to be O(N)--computing
the number of subscribers--is now O(1), and we
don't even pay O(N) on a one-time basis to
compute it (not counting the cost to build the
array from JSON, but we have to do that).
This commit is contained in:
@@ -9,6 +9,8 @@ const noop = function () {};
|
|||||||
set_global('$', global.make_zjquery());
|
set_global('$', global.make_zjquery());
|
||||||
set_global('i18n', global.stub_i18n);
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
||||||
|
const LazySet = zrequire('lazy_set.js').LazySet;
|
||||||
|
|
||||||
const _navigator = {
|
const _navigator = {
|
||||||
platform: '',
|
platform: '',
|
||||||
};
|
};
|
||||||
@@ -1328,13 +1330,13 @@ run_test('on_events', () => {
|
|||||||
(function test_stream_name_completed_triggered() {
|
(function test_stream_name_completed_triggered() {
|
||||||
const handler = $(document).get_on_handler('streamname_completed.zulip');
|
const handler = $(document).get_on_handler('streamname_completed.zulip');
|
||||||
stream_data.add_sub(compose_state.stream_name(), {
|
stream_data.add_sub(compose_state.stream_name(), {
|
||||||
subscribers: Dict.from_array([1, 2]),
|
subscribers: LazySet([1, 2]),
|
||||||
});
|
});
|
||||||
|
|
||||||
let data = {
|
let data = {
|
||||||
stream: {
|
stream: {
|
||||||
name: 'Denmark',
|
name: 'Denmark',
|
||||||
subscribers: Dict.from_array([1, 2, 3]),
|
subscribers: LazySet([1, 2, 3]),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1379,7 +1381,7 @@ run_test('on_events', () => {
|
|||||||
stream: {
|
stream: {
|
||||||
invite_only: true,
|
invite_only: true,
|
||||||
name: 'Denmark',
|
name: 'Denmark',
|
||||||
subscribers: Dict.from_array([1]),
|
subscribers: LazySet([1]),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ zrequire('marked', 'third/marked/lib/marked');
|
|||||||
const actual_pygments_data = zrequire('actual_pygments_data', 'generated/pygments_data');
|
const actual_pygments_data = zrequire('actual_pygments_data', 'generated/pygments_data');
|
||||||
zrequire('settings_org');
|
zrequire('settings_org');
|
||||||
const th = zrequire('typeahead_helper');
|
const th = zrequire('typeahead_helper');
|
||||||
|
const LazySet = zrequire('lazy_set.js').LazySet;
|
||||||
|
|
||||||
stream_data.create_streams([
|
stream_data.create_streams([
|
||||||
{name: 'Dev', subscribed: true, color: 'blue', stream_id: 1},
|
{name: 'Dev', subscribed: true, color: 'blue', stream_id: 1},
|
||||||
@@ -24,13 +25,9 @@ stream_data.create_streams([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
run_test('sort_streams', () => {
|
run_test('sort_streams', () => {
|
||||||
const popular = {num_items: function () {
|
const popular = LazySet([1, 2, 3, 4, 5, 6]);
|
||||||
return 10;
|
|
||||||
}};
|
|
||||||
|
|
||||||
const unpopular = {num_items: function () {
|
const unpopular = LazySet([1]);
|
||||||
return 2;
|
|
||||||
}};
|
|
||||||
|
|
||||||
let test_streams = [
|
let test_streams = [
|
||||||
{name: 'Dev', pin_to_top: false, subscribers: unpopular, subscribed: true},
|
{name: 'Dev', pin_to_top: false, subscribers: unpopular, subscribed: true},
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import "../search_util.js";
|
|||||||
import "../keydown_util.js";
|
import "../keydown_util.js";
|
||||||
import "../lightbox_canvas.js";
|
import "../lightbox_canvas.js";
|
||||||
import "../rtl.js";
|
import "../rtl.js";
|
||||||
|
import "../lazy_set.js";
|
||||||
import "../dict.ts";
|
import "../dict.ts";
|
||||||
import "../scroll_util.js";
|
import "../scroll_util.js";
|
||||||
import "../components.js";
|
import "../components.js";
|
||||||
|
|||||||
70
static/js/lazy_set.js
Normal file
70
static/js/lazy_set.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
exports.LazySet = function (vals) {
|
||||||
|
/*
|
||||||
|
This class is optimized for a very
|
||||||
|
particular use case.
|
||||||
|
|
||||||
|
We often have lots of subscribers on
|
||||||
|
a stream. We get an array from the
|
||||||
|
backend, because it's JSON.
|
||||||
|
|
||||||
|
Often the only operation we need
|
||||||
|
on subscribers is to get the length,
|
||||||
|
which is plenty cheap as an array.
|
||||||
|
|
||||||
|
Making an array from a set is cheap
|
||||||
|
for one stream, but it's expensive
|
||||||
|
for all N streams at page load.
|
||||||
|
|
||||||
|
Once somebody does an operation
|
||||||
|
where sets are useful, such
|
||||||
|
as has/add/del, we convert it over
|
||||||
|
to a set for a one-time cost.
|
||||||
|
*/
|
||||||
|
const self = {};
|
||||||
|
self.arr = vals;
|
||||||
|
self.set = undefined;
|
||||||
|
|
||||||
|
self.keys = function () {
|
||||||
|
if (self.set !== undefined) {
|
||||||
|
return Array.from(self.set);
|
||||||
|
}
|
||||||
|
return self.arr;
|
||||||
|
};
|
||||||
|
|
||||||
|
function make_set() {
|
||||||
|
if (self.set !== undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.set = new Set(self.arr);
|
||||||
|
self.arr = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.num_items = function () {
|
||||||
|
if (self.set !== undefined) {
|
||||||
|
return self.set.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.arr.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.map = function (f) {
|
||||||
|
return _.map(self.keys(), f);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.has = function (v) {
|
||||||
|
make_set();
|
||||||
|
return self.set.has(v);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.add = function (v) {
|
||||||
|
make_set();
|
||||||
|
self.set.add(v);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.del = function (v) {
|
||||||
|
make_set();
|
||||||
|
self.set.delete(v);
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
const Dict = require('./dict').Dict;
|
const Dict = require('./dict').Dict;
|
||||||
|
const LazySet = require('./lazy_set').LazySet;
|
||||||
|
|
||||||
|
|
||||||
// The stream_info variable maps stream names to stream properties objects
|
// The stream_info variable maps stream names to stream properties objects
|
||||||
@@ -74,7 +75,7 @@ exports.unsubscribe_myself = function (sub) {
|
|||||||
|
|
||||||
exports.add_sub = function (stream_name, sub) {
|
exports.add_sub = function (stream_name, sub) {
|
||||||
if (!_.has(sub, 'subscribers')) {
|
if (!_.has(sub, 'subscribers')) {
|
||||||
sub.subscribers = Dict.from_array([]);
|
sub.subscribers = LazySet([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
stream_info.set(stream_name, sub);
|
stream_info.set(stream_name, sub);
|
||||||
@@ -507,7 +508,7 @@ exports.maybe_get_stream_name = function (stream_id) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.set_subscribers = function (sub, user_ids) {
|
exports.set_subscribers = function (sub, user_ids) {
|
||||||
sub.subscribers = Dict.from_array(user_ids || []);
|
sub.subscribers = LazySet(user_ids || []);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.add_subscriber = function (stream_name, user_id) {
|
exports.add_subscriber = function (stream_name, user_id) {
|
||||||
@@ -521,7 +522,7 @@ exports.add_subscriber = function (stream_name, user_id) {
|
|||||||
blueslip.error("We tried to add invalid subscriber: " + user_id);
|
blueslip.error("We tried to add invalid subscriber: " + user_id);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
sub.subscribers.set(user_id, true);
|
sub.subscribers.add(user_id);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,12 +36,9 @@ exports.is_sub_settings_active = function (sub) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.get_email_of_subscribers = function (subscribers) {
|
exports.get_email_of_subscribers = function (subscribers) {
|
||||||
const emails = [];
|
return subscribers.map(function (user_id) {
|
||||||
subscribers.each(function (o, i) {
|
return people.get_person_from_user_id(user_id).email;
|
||||||
const email = people.get_person_from_user_id(i).email;
|
|
||||||
emails.push(email);
|
|
||||||
});
|
});
|
||||||
return emails;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function clear_edit_panel() {
|
function clear_edit_panel() {
|
||||||
|
|||||||
Reference in New Issue
Block a user