Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 597a0d91 authored by Yurii Zubrytskyi's avatar Yurii Zubrytskyi Committed by Android (Google) Code Review
Browse files

Merge changes I979e17cf,If9d92a28 into main

* changes:
  [res] Optimize Theme::ApplyStyle()
  [res] Add a Theme::Rebase() benchmark + fix data
parents 16574186 1e5452a0
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -212,6 +212,7 @@ cc_test {
        "tests/AttributeResolution_test.cpp",
        "tests/BigBuffer_test.cpp",
        "tests/ByteBucketArray_test.cpp",
        "tests/CombinedIterator_test.cpp",
        "tests/Config_test.cpp",
        "tests/ConfigDescription_test.cpp",
        "tests/ConfigLocale_test.cpp",
@@ -267,6 +268,7 @@ cc_test {
cc_benchmark {
    name: "libandroidfw_benchmarks",
    defaults: ["libandroidfw_defaults"],
    test_config: "tests/AndroidTest_Benchmarks.xml",
    srcs: [
        // Helpers/infra for benchmarking.
        "tests/BenchMain.cpp",
@@ -282,7 +284,11 @@ cc_benchmark {
        "tests/Theme_bench.cpp",
    ],
    shared_libs: common_test_libs,
    data: ["tests/data/**/*.apk"],
    data: [
        "tests/data/**/*.apk",
        ":FrameworkResourcesSparseTestApp",
        ":FrameworkResourcesNotSparseTestApp",
    ],
}

cc_library {
+67 −19
Original line number Diff line number Diff line
@@ -23,9 +23,11 @@
#include <map>
#include <set>
#include <span>
#include <utility>

#include "android-base/logging.h"
#include "android-base/stringprintf.h"
#include "androidfw/CombinedIterator.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/ResourceUtils.h"
#include "androidfw/Util.h"
@@ -1622,6 +1624,12 @@ Theme::Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) {

Theme::~Theme() = default;

static bool IsUndefined(const Res_value& value) {
  // DATA_NULL_EMPTY (@empty) is a valid resource value and DATA_NULL_UNDEFINED represents
  // an absence of a valid value.
  return value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY;
}

base::expected<std::monostate, NullOrIOError> Theme::ApplyStyle(uint32_t resid, bool force) {
  ATRACE_NAME("Theme::ApplyStyle");

@@ -1633,38 +1641,75 @@ base::expected<std::monostate, NullOrIOError> Theme::ApplyStyle(uint32_t resid,
  // Merge the flags from this style.
  type_spec_flags_ |= (*bag)->type_spec_flags;

  //
  // This function is the most expensive part of applying an frro to the existing app resources,
  // and needs to be as efficient as possible.
  // The data structure we're working with is two parallel sorted arrays of keys (resource IDs)
  // and entries (resource value + some attributes).
  // The styles get applied in sequence, starting with an empty set of attributes. Each style
  // contains its values for the theme attributes, and gets applied in either normal or forced way:
  //  - normal way never overrides the existing attribute, so only unique style attributes are added
  //  - forced way overrides anything for that attribute, and if it's undefined it removes the
  //    previous value completely
  //
  // Style attributes come in a Bag data type - a sorted array of attributes with their values. This
  // means we don't need to re-sort the attributes ever, and instead:
  //  - for an already existing attribute just skip it or apply the forced value
  //    - if the forced value is undefined, mark it undefined as well to get rid of it later
  //  - for a new attribute append it to the array, forming a new sorted section of new attributes
  //    past the end of the original ones (ignore undefined ones here)
  //  - inplace merge two sorted sections to form a single sorted array again.
  //  - run the last pass to remove all undefined elements
  //
  // Using this algorithm performs better than a repeated binary search + insert in the middle,
  // as that keeps shifting the tail end of the arrays and wasting CPU cycles in memcpy().
  //
  const auto starting_size = keys_.size();
  if (starting_size == 0) {
    keys_.reserve((*bag)->entry_count);
    entries_.reserve((*bag)->entry_count);
  }
  bool wrote_undefined = false;
  for (auto it = begin(*bag); it != end(*bag); ++it) {
    const uint32_t attr_res_id = it->key;

    // If the resource ID passed in is not a style, the key can be some other identifier that is not
    // a resource ID. We should fail fast instead of operating with strange resource IDs.
    if (!is_valid_resid(attr_res_id)) {
      return base::unexpected(std::nullopt);
    }

    // DATA_NULL_EMPTY (@empty) is a valid resource value and DATA_NULL_UNDEFINED represents
    // an absence of a valid value.
    bool is_undefined = it->value.dataType == Res_value::TYPE_NULL &&
        it->value.data != Res_value::DATA_NULL_EMPTY;
    const bool is_undefined = IsUndefined(it->value);
    if (!force && is_undefined) {
      continue;
    }

    const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), attr_res_id);
    const auto key_it = std::lower_bound(keys_.begin(), keys_.begin() + starting_size, attr_res_id);
    if (key_it != keys_.begin() + starting_size && *key_it == attr_res_id) {
      const auto entry_it = entries_.begin() + (key_it - keys_.begin());
    if (key_it != keys_.end() && *key_it == attr_res_id) {
      if (is_undefined) {
        // DATA_NULL_UNDEFINED clears the value of the attribute in the theme only when `force` is
        // true.
        keys_.erase(key_it);
        entries_.erase(entry_it);
      } else if (force) {
      if (force || IsUndefined(entry_it->value)) {
        *entry_it = Entry{it->cookie, (*bag)->type_spec_flags, it->value};
        wrote_undefined |= is_undefined;
      }
    } else {
      keys_.insert(key_it, attr_res_id);
      entries_.insert(entry_it, Entry{it->cookie, (*bag)->type_spec_flags, it->value});
    } else if (!is_undefined) {
      keys_.emplace_back(attr_res_id);
      entries_.emplace_back(it->cookie, (*bag)->type_spec_flags, it->value);
    }
  }

  if (starting_size && keys_.size() != starting_size) {
    std::inplace_merge(
        CombinedIterator(keys_.begin(), entries_.begin()),
        CombinedIterator(keys_.begin() + starting_size, entries_.begin() + starting_size),
        CombinedIterator(keys_.end(), entries_.end()));
  }
  if (wrote_undefined) {
    auto new_end = std::remove_if(CombinedIterator(keys_.begin(), entries_.begin()),
                                  CombinedIterator(keys_.end(), entries_.end()),
                                  [](const auto& pair) { return IsUndefined(pair.second.value); });
    keys_.erase(new_end.it1, keys_.end());
    entries_.erase(new_end.it2, entries_.end());
  }
  if (android::base::kEnableDChecks && !std::is_sorted(keys_.begin(), keys_.end())) {
    ALOGW("Bag %u was unsorted in the apk?", unsigned(resid));
    return base::unexpected(std::nullopt);
  }
  return {};
}
@@ -1691,6 +1736,9 @@ std::optional<AssetManager2::SelectedValue> Theme::GetAttribute(uint32_t resid)
      return std::nullopt;
    }
    const auto entry_it = entries_.begin() + (key_it - keys_.begin());
    if (IsUndefined(entry_it->value)) {
      return std::nullopt;
    }
    type_spec_flags |= entry_it->type_spec_flags;
    if (entry_it->value.dataType == Res_value::TYPE_ATTRIBUTE) {
      resid = entry_it->value.data;
+2 −2
Original line number Diff line number Diff line
@@ -280,9 +280,9 @@ class AssetManager2 {

   private:
    SelectedValue(uint8_t value_type, Res_value::data_type value_data, ApkAssetsCookie cookie,
                  uint32_t type_flags, uint32_t resid, const ResTable_config& config) :
                  uint32_t type_flags, uint32_t resid, ResTable_config config) :
                  cookie(cookie), data(value_data), type(value_type), flags(type_flags),
                  resid(resid), config(config) {};
                  resid(resid), config(std::move(config)) {}
  };

  // Retrieves the best matching resource value with ID `resid`.
+176 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#pragma once

#include <compare>
#include <iterator>
#include <utility>

namespace android {

namespace detail {
// A few useful aliases to not repeat them everywhere
template <class It1, class It2>
using Value = std::pair<typename std::iterator_traits<It1>::value_type,
                        typename std::iterator_traits<It2>::value_type>;

template <class It1, class It2>
using BaseRefPair = std::pair<typename std::iterator_traits<It1>::reference,
                              typename std::iterator_traits<It2>::reference>;

template <class It1, class It2>
struct RefPair : BaseRefPair<It1, It2> {
  using Base = BaseRefPair<It1, It2>;
  using Value = detail::Value<It1, It2>;

  RefPair(It1 it1, It2 it2) : Base(*it1, *it2) {
  }

  RefPair& operator=(const Value& v) {
    this->first = v.first;
    this->second = v.second;
    return *this;
  }
  operator Value() const {
    return Value(this->first, this->second);
  }
  bool operator==(const RefPair& other) {
    return this->first == other.first;
  }
  bool operator==(const Value& other) {
    return this->first == other.first;
  }
  std::strong_ordering operator<=>(const RefPair& other) const {
    return this->first <=> other.first;
  }
  std::strong_ordering operator<=>(const Value& other) const {
    return this->first <=> other.first;
  }
  friend void swap(RefPair& l, RefPair& r) {
    using std::swap;
    swap(l.first, r.first);
    swap(l.second, r.second);
  }
};

template <class It1, class It2>
struct RefPairPtr {
  RefPair<It1, It2> value;

  RefPair<It1, It2>* operator->() const {
    return &value;
  }
};
}  // namespace detail

//
//   CombinedIterator - a class to combine two iterators to process them as a single iterator to a
// pair of values. Useful for processing a data structure of "struct of arrays", replacing
// array of structs for cache locality.
//
// The value type is a pair of copies of the values of each iterator, and the reference is a
// pair of references to the corresponding values. Comparison only compares the first element,
// making it most useful for using on data like (vector<Key>, vector<Value>) for binary searching,
// sorting both together and so on.
//
// The class is designed for handling arrays, so it requires random access iterators as an input.
//

template <class It1, class It2>
requires std::random_access_iterator<It1> && std::random_access_iterator<It2>
struct CombinedIterator {
  typedef detail::Value<It1, It2> value_type;
  typedef detail::RefPair<It1, It2> reference;
  typedef std::ptrdiff_t difference_type;
  typedef detail::RefPairPtr<It1, It2> pointer;
  typedef std::random_access_iterator_tag iterator_category;

  CombinedIterator(It1 it1 = {}, It2 it2 = {}) : it1(it1), it2(it2) {
  }

  bool operator<(const CombinedIterator& other) const {
    return it1 < other.it1;
  }
  bool operator<=(const CombinedIterator& other) const {
    return it1 <= other.it1;
  }
  bool operator>(const CombinedIterator& other) const {
    return it1 > other.it1;
  }
  bool operator>=(const CombinedIterator& other) const {
    return it1 >= other.it1;
  }
  bool operator==(const CombinedIterator& other) const {
    return it1 == other.it1;
  }
  pointer operator->() const {
    return pointer{{it1, it2}};
  }
  reference operator*() const {
    return {it1, it2};
  }
  reference operator[](difference_type n) const {
    return {it1 + n, it2 + n};
  }

  CombinedIterator& operator++() {
    ++it1;
    ++it2;
    return *this;
  }
  CombinedIterator operator++(int) {
    const auto res = *this;
    ++*this;
    return res;
  }
  CombinedIterator& operator--() {
    --it1;
    --it2;
    return *this;
  }
  CombinedIterator operator--(int) {
    const auto res = *this;
    --*this;
    return res;
  }
  CombinedIterator& operator+=(difference_type n) {
    it1 += n;
    it2 += n;
    return *this;
  }
  CombinedIterator operator+(difference_type n) const {
    CombinedIterator res = *this;
    return res += n;
  }

  CombinedIterator& operator-=(difference_type n) {
    it1 -= n;
    it2 -= n;
    return *this;
  }
  CombinedIterator operator-(difference_type n) const {
    CombinedIterator res = *this;
    return res -= n;
  }
  difference_type operator-(const CombinedIterator& other) {
    return it1 - other.it1;
  }

  It1 it1;
  It2 it2;
};

}  // namespace android
+32 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2024 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->
<configuration description="Runs libandroidfw_benchmarks and libandroidfw_tests.">
    <option name="test-suite-tag" value="apct" />
    <option name="test-suite-tag" value="apct-native-metric" />

    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
        <option name="cleanup" value="true" />
        <option name="push" value="libandroidfw_benchmarks->/data/local/tmp/libandroidfw_benchmarks" />
    </target_preparer>
    <test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
        <option name="native-benchmark-device-path" value="/data/local/tmp" />
        <option name="benchmark-module-name" value="libandroidfw_benchmarks" />
        <!-- The GoogleBenchmarkTest class ordinarily expects every file in the benchmark's
             directory (recursively) to be a google-benchmark binary, so we need this setting to
             avoid failing on the test data files. -->
        <option name="file-exclusion-filter-regex" value=".*\.(apk|config)$"  />
    </test>
</configuration>
 No newline at end of file
Loading