一次失败的SP优化
来源:互联网 发布:室内分布设计软件 编辑:程序博客网 时间:2024/05/20 23:56
去到了一个新公司,发现一个神奇的操作:所有数据都是存在同一个sp中的。从源码上看,sp会在new的时候启动异步线程读取整个文件,并parse成map,这会阻塞第一个读写操作;而每次commit/apply的时候,都会写入全量文件。看起来这里有一个性能问题。本着无聊+装逼的心情,做了一个基于一致性哈希的SP。
public class ConsistentHashPreferences implements SharedPreferences { private PreferenceConfig mPreferenceConfig; public ConsistentHashPreferences(PreferenceConfig config) { mPreferenceConfig = config; } @Override public Map<String, ?> getAll() { Map<String, Object> map = new HashMap<>(); for (SharedPreferences sp : mPreferenceConfig.all()) { map.putAll(sp.getAll()); } return map; } @Nullable @Override public String getString(String key, @Nullable String defValue) { return mPreferenceConfig.preferenceForKey(key).getString(key, defValue); } @Nullable @Override public Set<String> getStringSet(String key, @Nullable Set<String> defValues) { return null; } @Override public int getInt(String key, int defValue) { return mPreferenceConfig.preferenceForKey(key).getInt(key, defValue); } @Override public long getLong(String key, long defValue) { return mPreferenceConfig.preferenceForKey(key).getLong(key, defValue); } @Override public float getFloat(String key, float defValue) { return mPreferenceConfig.preferenceForKey(key).getFloat(key, defValue); } @Override public boolean getBoolean(String key, boolean defValue) { return mPreferenceConfig.preferenceForKey(key).getBoolean(key, defValue); } @Override public boolean contains(String key) { return mPreferenceConfig.preferenceForKey(key).contains(key); } @Override public Editor edit() { return new EditorCache(); } @Override public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { } @Override public void unregisterOnSharedPreferenceChangeListener( OnSharedPreferenceChangeListener listener) { } private class EditorCache implements SharedPreferences.Editor { private Map<Integer, Editor> mEditorCache; private List<TranscationItem> mTransactions; private Editor obtainEditor(String key) { if (mEditorCache == null) { mEditorCache = new HashMap<>(); } Editor editor = mEditorCache.get(mPreferenceConfig.hashForKey(key)); if (editor == null) { editor = mPreferenceConfig.preferenceForKey(key).edit(); } mEditorCache.put(mPreferenceConfig.hashForKey(key), editor); return editor; } public EditorCache() { init(); } private void init() { mTransactions = new ArrayList<>(); } @Override public SharedPreferences.Editor putString(String key, @Nullable String value) { mTransactions.add(new StringTransactionItem(key, value)); return this; } @Override public SharedPreferences.Editor putStringSet(String key, @Nullable Set<String> values) { return null; } @Override public SharedPreferences.Editor putInt(String key, int value) { mTransactions.add(new IntegerTransactionItem(key, value)); return this; } @Override public SharedPreferences.Editor putLong(String key, long value) { mTransactions.add(new LongTransactionItem(key, value)); return this; } @Override public SharedPreferences.Editor putFloat(String key, float value) { mTransactions.add(new FloatTransactionItem(key, value)); return this; } @Override public SharedPreferences.Editor putBoolean(String key, boolean value) { mTransactions.add(new BooleanTransactionItem(key, value)); return this; } @Override public SharedPreferences.Editor remove(String key) { mTransactions.add(forRemove(key)); return this; } @Override public SharedPreferences.Editor clear() { // TODO: 2017/7/1 foreach return this; } @Override public boolean commit() { boolean success = doCommit(); if (!success) { doRevert(); } init(); return success; } private void doRevert() { for (TranscationItem transcationItem : mTransactions) { transcationItem.revert(obtainEditor(transcationItem.key)); } for (Editor editor : mEditorCache.values()) { editor.commit(); } } private boolean doCommit() { for (TranscationItem transcationItem : mTransactions) { transcationItem.commit(obtainEditor(transcationItem.key)); } boolean success = true; for (Editor editor : mEditorCache.values()) { success &= editor.commit(); } return success; } @Override public void apply() { new Thread() { @Override public void run() { super.run(); commit(); } }.start(); } } private abstract class TranscationItem<T> { public String key; private Pair<T, T> mData; public TranscationItem(String key, T to) { this.key = key; T from = oldValue(); mData = new Pair<>(from, to); } public void commit(Editor editor) { if (mData.second == null) { editor.remove(key); } else { toEditor(editor, mData.second); } } public void revert(Editor editor) { if (mData.first == null) { editor.remove(key); } else { toEditor(editor, mData.first); } } protected abstract void toEditor(Editor editor, T value); protected abstract T oldValue(); } private class StringTransactionItem extends TranscationItem<String> { public StringTransactionItem(String key, String to) { super(key, to); } @Override protected void toEditor(Editor editor, String value) { editor.putString(key, value); } @Override protected String oldValue() { return mPreferenceConfig.preferenceForKey(key).getString(key, null); } } private class IntegerTransactionItem extends TranscationItem<Integer> { public IntegerTransactionItem(String key, Integer to) { super(key, to); } @Override protected void toEditor(Editor editor, Integer value) { editor.putInt(key, value); } @Override protected Integer oldValue() { return mPreferenceConfig.preferenceForKey(key).getInt(key, 0); } } private class LongTransactionItem extends TranscationItem<Long> { public LongTransactionItem(String key, Long to) { super(key, to); } @Override protected void toEditor(Editor editor, Long value) { editor.putLong(key, value); } @Override protected Long oldValue() { return mPreferenceConfig.preferenceForKey(key).getLong(key, 0); } } private class FloatTransactionItem extends TranscationItem<Float> { public FloatTransactionItem(String key, Float to) { super(key, to); } @Override protected void toEditor(Editor editor, Float value) { editor.putFloat(key, value); } @Override protected Float oldValue() { return mPreferenceConfig.preferenceForKey(key).getFloat(key, 0); } } private class BooleanTransactionItem extends TranscationItem<Boolean> { public BooleanTransactionItem(String key, Boolean to) { super(key, to); } @Override protected void toEditor(Editor editor, Boolean value) { editor.putBoolean(key, value); } @Override protected Boolean oldValue() { return mPreferenceConfig.preferenceForKey(key).getBoolean(key, false); } } private TranscationItem forRemove(String key) { if (TextUtils.isEmpty(key) || !mPreferenceConfig.preferenceForKey(key).contains(key)) { return null; } Object value = mPreferenceConfig.preferenceForKey(key).getAll().get(key); if (value instanceof String) { return new StringTransactionItem(key, null); } else if (value instanceof Integer) { return new IntegerTransactionItem(key, null); } else if (value instanceof Long) { return new LongTransactionItem(key, null); } else if (value instanceof Float) { return new FloatTransactionItem(key, null); } else if (value instanceof Boolean) { return new BooleanTransactionItem(key, null); } return null; }}
public class PreferenceConfig { private static final int PREFERENCE_SIZE = 10; private static final String KEY_VERSION = "chp_version"; private String mPrefix = "Kwai_"; private SharedPreferences mNameMappings; private SparseArray<String> mPreferencesMappings; private Map<String, Integer> mHashMappings; private Map<String, SharedPreferences> mPreferences = new HashMap<>(); private Context mContext; private int mVersion; public PreferenceConfig(String prefix, String configName, Context context, SparseArray<String> preferencesMappings, Map<String, Integer> hashMappings, int version) { mPrefix = prefix; mNameMappings = context.getSharedPreferences(mPrefix + configName, Context.MODE_PRIVATE); mPreferencesMappings = genPreferencesMappings(preferencesMappings); mHashMappings = hashMappings; mVersion = version; mContext = context; checkAndUpgrade(); } @NonNull private SparseArray<String> genPreferencesMappings(SparseArray<String> preferencesMappings) { SparseArray<String> mappings = preferencesMappings == null ? new SparseArray<String>() : preferencesMappings; String last = null; int firstIndex = 0; for (int i = 0; i < PREFERENCE_SIZE; ++i) { if (!TextUtils.isEmpty(last = mappings.get(i))) { firstIndex = i; break; } } if (TextUtils.isEmpty(last)) { last = mPrefix; } for (int i = PREFERENCE_SIZE + firstIndex; i > firstIndex; --i) { if (TextUtils.isEmpty(mappings.get((i - 1) % PREFERENCE_SIZE))) { mappings.put((i - 1) % PREFERENCE_SIZE, last); } else { last = mappings.get((i - 1) % PREFERENCE_SIZE); } } return mappings; } private String nameForIndex(int i) { String name = mPreferencesMappings.get(i); return TextUtils.isEmpty(name) ? mPrefix : name; } int hashForKey(String key) { if (mHashMappings == null) { return key.hashCode(); } Integer mapping = mHashMappings.get(key); return mapping == null ? key.hashCode() : mapping; } SharedPreferences preferenceForKey(String key) { int hash = hashForKey(key); String name = nameForIndex(hash); return getSharedPreferencesByName(name); } private SharedPreferences getSharedPreferencesByName(String name) { if (mPreferences.get(name) == null) { mPreferences.put(name, mContext .getSharedPreferences(name, Context.MODE_MULTI_PROCESS)); } return mPreferences.get(name); } private void checkAndUpgrade() { int oldVersion = mNameMappings.getInt(KEY_VERSION, 0); if (mVersion <= oldVersion) { mPreferencesMappings = new SparseArray<>(); for (int i = 0; i < PREFERENCE_SIZE; ++i) { mPreferencesMappings.put(i, mNameMappings.getString(String.valueOf(i), null)); } return; } // 迁移sp if (!transferSp()) { return; } // 迁移group if (!transferGroup()) { return; } commitVersion(); } private boolean commitVersion() { SharedPreferences.Editor editor = mNameMappings.edit(); editor.putInt(KEY_VERSION, mVersion); for (int i = 0; i < PREFERENCE_SIZE; ++i) { editor.putString(String.valueOf(i), mPreferencesMappings.get(i)); } return editor.commit(); } private boolean transferGroup() { // 尽量减少commit次数,重复缓存了很多东西 HashMap<String, Map<String, ?>> oldDataCache = new HashMap<>(); HashMap<String, Set<String>> toRemove = new HashMap<>(); HashMap<String, Map<String, Object>> toAdd = new HashMap<>(); for (String key : mHashMappings.keySet()) { if (preferenceForKey(key).contains(key)) { continue; } String oldName = nameForIndex(key.hashCode()); if (oldDataCache.get(oldName) == null) { oldDataCache.put(oldName, preferenceForKey(oldName).getAll()); } String newName = nameForIndex(hashForKey(key)); if (toAdd.get(newName) == null) { toAdd.put(newName, new HashMap<String, Object>()); } toAdd.get(newName).put(key, oldDataCache.get(oldName).get(key)); if (toRemove.get(oldName) == null) { toRemove.put(oldName, new HashSet<String>()); } toRemove.get(oldName).add(key); } boolean success = true; for (String name : toAdd.keySet()) { SharedPreferences.Editor editor = getSharedPreferencesByName(name).edit(); Map<String, Object> data = toAdd.get(name); for (String key : data.keySet()) { putByType(editor, key, data.get(key)); } success &= editor.commit(); } if (!success) { return false; } for (String name : toRemove.keySet()) { SharedPreferences.Editor editor = getSharedPreferencesByName(name).edit(); Set<String> keys = toRemove.get(name); for (String key : keys) { editor.remove(key); } success &= editor.commit(); } return success; } private boolean transferSp() { // 目标sp只会对应到一个源sp HashMap<String, String> transferMapping = new HashMap<>(); for (int i = 0; i < PREFERENCE_SIZE; ++i) { String oldName = mNameMappings.getString(String.valueOf(i), null); String name = mPreferencesMappings.get(i); if (!TextUtils.equals(oldName, name)) { // 不同sp if (oldName == null) { oldName = mPrefix; } transferMapping.put(name, oldName); } } boolean success = true; for (String newName : transferMapping.keySet()) { Set<String> toRemove = new HashSet<>(); SharedPreferences oldPreferences = getSharedPreferencesByName(transferMapping.get(newName)); Map<String, ?> oldData = oldPreferences.getAll(); SharedPreferences.Editor oldEditor = oldPreferences.edit(); SharedPreferences.Editor newEditor = getSharedPreferencesByName(newName).edit(); for (String key : oldData.keySet()) { if (TextUtils.equals(nameForIndex(hashForKey(key)), newName)) { toRemove.add(key); putByType(newEditor, key, oldData.get(key)); } } if (newEditor.commit()) { for (String key : toRemove) { oldEditor.remove(key); } success &= oldEditor.commit(); } else { success = false; } } return success; } private void putByType(SharedPreferences.Editor editor, String key, Object value) { if (TextUtils.isEmpty(key) || value == null) { return; } if (value instanceof String) { editor.putString(key, (String) value); } else if (value instanceof Integer) { editor.putInt(key, (Integer) value); } else if (value instanceof Long) { editor.putLong(key, (Long) value); } else if (value instanceof Float) { editor.putFloat(key, (Float) value); } else if (value instanceof Boolean) { editor.putBoolean(key, (Boolean) value); } } public Collection<SharedPreferences> all() { return mPreferences.values(); }}
ConsistentHash算法就不用多说了,用在sp上的坑也是有的:
- 升级和分块的控制,即具体sp实体放到哪个hashcode。此处仿照DB的version思路。
- commit的原子性。一次edit到commit之间的所有操作应该是一个transaction。使用了command模式。这里是性能瓶颈,出现了太多的额外对象创建和Map扩展。最终导致了悲剧。
最后做了性能测试,发现一致性哈希完败。千行数据,只有在单行超30字符的时候,读取能够持平;写入有百倍的劣化。而且,最尴尬的是,sp本身的数据相当亮眼,并没有优化的意义。这么尴尬的事情,记录一下吧。
阅读全文
0 0
- 一次失败的SP优化
- 一次入侵的失败
- 一次失败的经历
- 一次失败的面试
- 一次失败的比赛
- 一次失败的视频面试
- 一次失败的考试总结
- 一次失败的WebService布署
- 一次面试失败的反思
- 一次失败的软件演示
- 一次失败的淘宝经历
- 一次失败的兼职经历
- 一次失败的开发体验
- DexClassLoader一次失败的尝试
- 记一次失败的面试
- 一次失败的j2v8集成
- 记一次失败的比赛
- 记一次失败的面试
- 无法启动链接服务器 "XXXXXX" 的 OLE DB 访问接口 "MSDASQL" 的嵌套事务。由于 XACT_ABORT 选项已设置为 OFF,因此必须使用嵌套事务。
- CodeForces 370 B.Berland Bingo(模拟)
- 【NOI2002】caioj1095: 并查集4(银河英雄传说)
- 循环队列理解及练习
- AndroidStudio 打包apk笔记
- 一次失败的SP优化
- unix环境高级编程-互斥量机制
- 1135: 算菜价
- MySQL 数据库性能优化之表结构优化
- 原生js中的事件委托(为新添加的DOM元素添加事件)
- redis高级应用
- 项目要求url打开android应用
- Python 全局变量与局部变量
- 格式