【问题标题】:Flutter Objectbox performance issueFlutter Objectbox 性能问题
【发布时间】:2021-04-09 01:03:17
【问题描述】:

如果有人有解决方案,请帮助我。

现在我遇到了 Flutter 中 ObjectBox 的性能问题。 我有EventRecurrenceEventPlace的模型,它们都有关系。

问题是数据插入耗时太长,导致应用崩溃,因为操作在主线程中堆栈直到完成。

完成 1 个循环大约需要 300 毫秒...

我不知道如何解决这个问题。我不应该使用模型之间的关系吗? 还是我使用 Objectbox 的方法有误?

[✓] Flutter (Channel stable, 1.22.5, on Mac OS X 10.15.7 19H524 darwin-x64, locale
    en-GB)
 
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
[✓] Xcode - develop for iOS and macOS (Xcode 12.4)
[✓] Android Studio (version 4.1)
[✓] VS Code (version 1.54.3)
[✓] Connected device (1 available)
@Entity(uid: EVENT_UID)
class ObEvent {
  @Id(assignable: true)
  int id;

  String deviceEventId;
  String eventTitle;
  String eventDesc;
  String eventType;
  List<String> eventLabels;
  String timezone;
  bool isAllDay;
  String link;
  int recurrId;
  int originalRecurrId;
  bool toDateUnfixed;
  bool isOwned;
  bool editable;

  @Property(type: PropertyType.byteVector)
  List<int> remainders;

  @Index()
  int calendarId;

  @Index()
  int parentId;

  @Index()
  String usersId;

  @Index()
  @Property(type: PropertyType.date)
  DateTime eventPublishDate;

  @Index()
  @Property(type: PropertyType.date)
  DateTime eventStart;

  @Index()
  @Property(type: PropertyType.date)
  DateTime eventEnd;

  @Index()
  @Property(type: PropertyType.date)
  DateTime createdAt;

  @Index()
  @Property(type: PropertyType.date)
  DateTime updatedAt;

  @Index()
  bool deleted;

  @Index()
  bool notSynced;

  final recurrence = ToOne<ObRecurrence>();
  final place = ToOne<ObEventPlace>();

  ObEvent({
    this.id,
    this.usersId,
    this.calendarId,
    this.eventTitle,
    this.eventStart,
    this.eventEnd,
    this.deviceEventId,
    this.editable,
    this.eventType,
    this.isAllDay,
    this.toDateUnfixed,
    this.eventDesc,
    this.parentId,
    this.eventLabels,
    this.eventPublishDate,
    this.link,
    this.remainders,
    this.recurrId,
    this.originalRecurrId,
    this.createdAt,
    this.updatedAt,
    this.isOwned = true,
    this.deleted = false,
    this.notSynced = false,
    timezone,
  }) : timezone = getIt<Vars>().defaultTimezone;

  ObEvent.fromEvent(Event event)
      : this.id = event.id,
        this.usersId = event.userId,
        this.calendarId = event.calendar.calId,
        this.eventTitle = event.eventTitle,
        this.eventStart = event.eventStart,
        this.eventEnd = event.eventEnd,
        this.deviceEventId = event.deviceEventId,
        this.editable = event.editable,
        this.eventType = EnumHandler.enumToString(event.eventType),
        this.isAllDay = event.isAllDay,
        this.toDateUnfixed = event.toDateUnfixed,
        this.eventDesc = event.eventDesc,
        this.parentId = event.parentId,
        this.eventLabels = event.eventLabels,
        this.eventPublishDate = event.eventPublishDate,
        this.link = event.link,
        this.remainders = event.remainders?.toList(),
        this.recurrId = event.recurrId,
        this.originalRecurrId = event.originalRecurrId,
        this.isOwned = event.isOwned,
        this.createdAt = event.createdAt,
        this.updatedAt = event.updatedAt,
        this.timezone = event.timezone;

  ObEvent merge(ObEvent event) {
    return ObEvent(
      id: event.id ?? this.id,
      usersId: event.usersId ?? this.usersId,
      calendarId: event.calendarId ?? this.calendarId,
      eventTitle: event.eventTitle ?? this.eventTitle,
      eventStart: event.eventStart ?? this.eventStart,
      eventEnd: event.eventEnd ?? this.eventEnd,
      deviceEventId: event.deviceEventId ?? this.deviceEventId,
      editable: event.editable ?? this.editable,
      eventType: event.eventType ?? this.eventType,
      isAllDay: event.isAllDay ?? this.isAllDay,
      toDateUnfixed: event.toDateUnfixed ?? this.toDateUnfixed,
      eventDesc: event.eventDesc ?? this.eventDesc,
      parentId: event.parentId ?? this.parentId,
      eventLabels: event.eventLabels ?? this.eventLabels,
      eventPublishDate: event.eventPublishDate ?? this.eventPublishDate,
      link: event.link ?? this.link,
      remainders: event.remainders ?? this.remainders,
      recurrId: event.recurrId ?? this.recurrId,
      originalRecurrId: event.originalRecurrId ?? this.originalRecurrId,
      isOwned: event.isOwned ?? this.isOwned,
      createdAt: event.createdAt ?? this.createdAt,
      updatedAt: event.updatedAt ?? this.updatedAt,
      timezone: event.timezone ?? this.timezone,
      deleted: event.deleted ?? this.deleted,
      notSynced: event.notSynced ?? this.notSynced,
    );
  }

  // For event type
  EventType get type => eventType.toEnum(EventType.values);
  set type(EventType value) => eventType = EnumHandler.enumToString(value);
}
@Entity(uid: EVENT_PLACE_UID)
class ObEventPlace {
  @Id()
  int id;

  String address;
  double lat;
  double lon;

  ObEventPlace({
    this.id,
    this.address,
    this.lat,
    this.lon,
  });

  ObEventPlace.fromEventPlace(EventPlace place)
      : this.address = place.address,
        this.lat = place.lat,
        this.lon = place.lon;

  ObEventPlace.fromMap(Map data)
      : this.address = data['address'],
        this.lat = data['lat'],
        this.lon = data['lon'];

  ObEventPlace merge(ObEventPlace ob) {
    return ObEventPlace(
      id: ob.id ?? this.id,
      address: ob.address ?? this.address,
      lat: ob.lat ?? this.lat,
      lon: ob.lon ?? this.lon,
    );
  }
}
@Entity(uid: RECURRENCE_UID)
class ObRecurrence {
  @Id(assignable: true)
  int id;

  String freq;
  int intval;
  List<String> byDay;
  String timezone;

  @Property(type: PropertyType.date)
  DateTime dtStart;
  @Property(type: PropertyType.date)
  DateTime dtEnd;
  @Property(type: PropertyType.byteVector)
  List<int> byMonth;
  @Property(type: PropertyType.byteVector)
  List<int> byMonthDay;
  @Property(type: PropertyType.byteVector)
  List<int> setPositions;
  @Property(type: PropertyType.byteVector)
  List<int> exDates;

  ObRecurrence({
    this.id,
    this.freq,
    this.intval,
    this.dtStart,
    this.dtEnd,
    this.byDay,
    this.byMonth,
    this.byMonthDay,
    this.setPositions,
    this.timezone,
    this.exDates,
  });

  ObRecurrence.fromRecurr(Recurrence recurr)
      : this.id = recurr.id,
        this.freq = EnumHandler.enumToString(recurr.freq),
        this.intval = recurr.intval,
        this.dtStart = recurr.dtStart,
        this.dtEnd = recurr.dtEnd,
        this.byDay = recurr.byDay?.map((day) => EnumHandler.enumToString(day))?.toList(),
        this.byMonth = recurr.byMonth?.toList(),
        this.byMonthDay = recurr.byMonthDay?.toList(),
        this.setPositions = recurr.setPositions?.toList(),
        this.timezone = recurr.timezone,
        this.exDates = recurr.exDates?.map((date) => date.millisecondsSinceEpoch)?.toList();

  ObRecurrence merge(ObRecurrence rec) {
    return ObRecurrence(
      id: rec.id ?? this.id,
      freq: rec.freq ?? this.freq,
      intval: rec.intval ?? this.intval,
      dtStart: rec.dtStart ?? this.dtStart,
      dtEnd: rec.dtEnd ?? this.dtEnd,
      byDay: rec.byDay ?? this.byDay,
      byMonth: rec.byMonth ?? this.byMonth,
      byMonthDay: rec.byMonthDay ?? this.byMonthDay,
      setPositions: rec.setPositions ?? this.setPositions,
      timezone: rec.timezone ?? this.timezone,
      exDates: rec.exDates ?? this.exDates,
    );
  }

  // For freq
  Freq get frequency => freq.toEnum(Freq.values);
  set frequency(Freq value) => EnumHandler.enumToString(value);

  // For byDay field
  Set<Day> get bydayList => byDay?.map((day) => day.toEnum(Day.values))?.toSet() ?? Set<Day>();
  set bydayList(Set<Day> value) => byDay = value.map((day) => EnumHandler.enumToString(day)).toList();

  // For exdates field
  Set<DateTime> get exdates =>
      exDates?.map((date) => DateTime.fromMillisecondsSinceEpoch(date))?.toSet() ?? Set<DateTime>();
  set exdates(Set<DateTime> value) => exDates = value.map((date) => date.millisecondsSinceEpoch).toList();

  MonthlyType get monthlyType => frequency == Freq.MONTHLY
      ? setPositions.isEmpty
          ? MonthlyType.BY_DATE
          : MonthlyType.BY_WEEK_DAY
      : MonthlyType.BY_DATE;
}
class Repository {
  Store store;
  Box<ObCalendar> _calBox;
  Box<ObEvent> _eventBox;
  Box<ObCalendarLink> _calLinkBox;
  Box<ObTag> _tagBox;
  Box<ObEventPlace> _placeBox;

  CalendarRepository({
    @required this.store,
  })  : this._calBox = store.box<ObCalendar>(),
        this._eventBox = store.box<ObEvent>(),
        this._calLinkBox = store.box<ObCalendarLink>(),
        this._tagBox = store.box<ObTag>(),
        this._placeBox = store.box<ObEventPlace>(),
        this._recBox = store.box<ObObRecurrence>();

  cacheEvents(data, user) async {
    final _obEvents = data.fold(<ObEvent>[], (prev, event) {
      // This is the modal used in application and properties are the same with ObEvent class
      final Event _event = Event.fromMap(user?.id, event);
      final ObEvent _obOldEvent = _eventBox.get(_event.id) ?? ObEvent();
      final ObEvent _obNewEvent = _obOldEvent.merge(ObEvent.fromEvent(_event))
        ..deleted = false
        ..notSynced = false;

      // No update
      if (_obOldEvent.id != null && (_event.updatedAt?.compareTo(_obNewEvent.updatedAt) ?? 0) == 0) {
        return prev;
      }

      // Update place cache of event
      if (_event.place != null) {
        final ObEventPlace _oldPlace = _obNewEvent.place.target ?? ObEventPlace();
        final ObEventPlace _newPlace = _oldPlace.merge(_oldPlace);

        _placeBox.put(_newPlace);
        _obNewEvent.place.target = _newPlace;
      }

      // Cache reucrrence if exists
      if (_event.recurrence != null) {
        final ObRecurrence _obOldRecurr = _obNewEvent.recurrence.target ?? ObRecurrence();
        final ObRecurrence _obNewRecurr = _obOldRecurr.merge(ObRecurrence.fromRecurr(_event.recurrence));

        _recBox.put(_obNewRecurr);
        _obNewEvent..recurrence.target = _obNewRecurr;
      }

      prev.add(_obNewEvent);

      return prev;
    });

    _eventBox.putMany(_obEvents);
  }
}

我暂时在其他线程中使用compute 执行此操作以防止崩溃,但如果我真的能获得基准文档所说的性能,我不需要这样做。

【问题讨论】:

    标签: flutter dart objectbox


    【解决方案1】:

    我能看到的最大问题是在循环中执行许多写操作 (put()) 时不使用 Transaction。为此,请使用Store.runInTransaction()。此外,ObjectBox-Java documentation 中有更多信息,它也适用于 Dart。

    简而言之,您可以将整个 cacheEvents() 包装在一个写入事务中,例如:

    cacheEvents(data, user) => store.runInTransaction(TxMode.write, () {
      // existing code
    });
    

    在这种情况下,您实际上不需要使用 putMany(),它只是一个实用函数,如果您只插入一个“实体”,它会为您执行事务。您可以在事务中使用纯 put()。所以你的代码可能看起来像这样(未经测试,只是在我的脑海中):

    cacheEvents(data, user) =>
        store.runInTransaction(TxMode.write, () =>
            data.forEach((event) {
              // This is the modal used in application and properties are the same with ObEvent class
              final Event _event = Event.fromMap(user?.id, event);
              final ObEvent _obOldEvent = _eventBox.get(_event.id) ?? ObEvent();
              final ObEvent _obNewEvent = _obOldEvent.merge(ObEvent.fromEvent(_event))
                ..deleted = false
                ..notSynced = false;
    
              // No update
              if (_obOldEvent.id != null && (_event.updatedAt?.compareTo(_obNewEvent.updatedAt) ?? 0) == 0) {
                return;
              }
    
              // Update place cache of event
              if (_event.place != null) {
                final ObEventPlace _oldPlace = _obNewEvent.place.target ?? ObEventPlace();
                final ObEventPlace _newPlace = _oldPlace.merge(_oldPlace);
    
                _placeBox.put(_newPlace);
                _obNewEvent.place.target = _newPlace;
              }
    
              // Cache reucrrence if exists
              if (_event.recurrence != null) {
                final ObRecurrence _obOldRecurr = _obNewEvent.recurrence.target ?? ObRecurrence();
                final ObRecurrence _obNewRecurr = _obOldRecurr.merge(ObRecurrence.fromRecurr(_event.recurrence));
    
                _recBox.put(_obNewRecurr);
                _obNewEvent..recurrence.target = _obNewRecurr;
              }
              _eventBox.put(_obNewEvent);
            }));
    

    还有一些其他建议/问题,尽管它们不会产生如此大的影响:

    • 考虑更改 .merge() 以实际更新 this 对象,而不是创建一个新对象
    • 考虑是否需要所有这些索引:索引虽然有利于查询,但也会增加插入/更新数据的时间,因为每个索引也需要更新。您是否对所有索引属性使用条件查询?
    • 您也可以使用 eventBox.getMany() 来首先获取您将要接触的所有事件 - 尽管在这种情况下这可能不会对性能产生太大影响

    顺便说一句,为什么你有所有这些“桥”类,比如ObEvent,而不是将Event 定义为@Entity()?很高兴知道 ObjectBox 方面是否可以做一些事情,这样您就不必这样做了。

    【讨论】:

    • 感谢您的评论,性能得到了绝对的提升! Java插件的文档我没看过。
    • 关于 Bridge class 的问题,因为我通过扩展 Equatable 创建了我的 Event 类,并且所有属性都定义为 final 我不能将它用于模型Objectbox 所以我选择制作它而不是修改整个项目。实际上应该更好地减少一些索引,因为它们暂时没有按照您的建议使用。
    • 已将您的评论添加到github.com/objectbox/objectbox-dart/issues/193,所以也许可以解决
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-27
    • 2020-06-08
    • 1970-01-01
    • 1970-01-01
    • 2020-08-11
    • 2021-09-05
    • 1970-01-01
    相关资源
    最近更新 更多