[✔️]cocos2dx Tiledmap性能优化分析:layer data建议使用压缩格式gzip/zlib

349 阅读2分钟

项目中有一个tmx文件,16M,加载这个tiledmap的时候,非常慢非常慢,在VisualStudio中通过性能探查器,进行了一波分析: image.png

从饼状图中可以看到性能压力集中在文件IO上,追查到性能热点代码,是在解析csv数据:

unsigned char *buffer;

TMXLayerInfo* layer = tmxMapInfo->getLayers().back();

tmxMapInfo->setStoringCharacters(false);
std::string currentString = tmxMapInfo->getCurrentString();

vector<string> gidTokens;
istringstream filestr(currentString);
string sRow;
// 因为地图是1000*1000,好在csv数据只有一行,所以这里只会执行一次
while(getline(filestr, sRow, '\n')) {
    string sGID;
    istringstream rowstr(sRow);
    // 但是因为csv是通过,分割,所以间接导致gidTokens会非常的大
    // 性能热点函数getline
    while (getline(rowstr, sGID, ',')) {
        gidTokens.push_back(sGID);
    }
}

// 32-bits per gid
buffer = (unsigned char*)malloc(gidTokens.size() * 4);
if (!buffer)
{
    CCLOG("cocos2d: TiledMap: CSV buffer not allocated.");
    return;
}

uint32_t* bufferPtr = reinterpret_cast<uint32_t*>(buffer);
// 这里是百万次以上循环,有一定的性能压力
for(auto gidToken : gidTokens) {
    auto tileGid = (uint32_t)strtoul(gidToken.c_str(), nullptr, 10);
    *bufferPtr = tileGid;
    bufferPtr++;
}

layer->_tiles = reinterpret_cast<uint32_t*>(buffer);

tmxMapInfo->setCurrentString("");

getline 是 C++ 标准库中的一个函数,其作用是从输入流中读取一行数据并存储到指定的字符串对象中。

std::istream& getline(std::istream& is, std::string& str, char delim);

其中,is 表示输入流对象,str 表示要存储读取结果的字符串对象,delim 表示分隔符(默认为换行符 \n)。当遇到分隔符时,getline 函数会停止读取并返回流对象。该函数会忽略分隔符本身,并将其从输入流缓冲区中删除。

getline 函数在输入流中读取一行数据时,需要逐个字符地读取,并检查每个字符是否为分隔符。由于字符的读取和检查是严格按照顺序进行的,因此这种方法会导致在处理大量数据时的性能问题。

不同encoding对比

encoding第1次/第2次size
csv21812ms/21508ms16.2M
base641670ms /610ms42.1M
zlib1183ms/105ms471K
gzip1189ms/130ms472k

以上是测试超大tiledmap地图的加载性能报告,观察到一个非常有趣的现象,zlib的第1次加载和第2次加载耗费的时间差距有点大,初步猜测是因为加载纹理导致的

  • 第1次加载时,可以看到在addImage上花费了大量时间 image.png

  • 第2次加载时,在xml解析上花费了大量时间: image.png

这个结果是符合我们的猜测,所以如果要加快tmx的创建,可以预加载tmx所使用的纹理图片。

结论

超大地图,尽量保存采用zlib、gzip的方式保存数据,会降低IO操作,提高tiledmap的性能,同时减少tmx文件大小,理论上这个问题在cocos creator中也会遇到。