第22.3节 性能篇-共享结点

天下武功,唯快不破

最近网友问了关于点云、倾斜摄影数据的性能优化问题。本来想刀枪剑戟、斧钺勾叉给弄了,但是后来想性能其实是个系统问题,要在第22节分成数小节扎扎实实的讲一讲。

鸣谢

非常感谢王锐王大神的cookbook,我准备主要参考它里面关于性能的一章。也就是第8章。本节讲述性能优化的最基本的手段:共享结点。

本节资源

本文集包括本节所有资源包括模型代码都在此下载,按节的序号有文件或文件夹:

注意:务必使用浏览器打开:
链接:https://pan.baidu.com/s/13gwJLwo_LbRnN3Bl2NXXXw
提取码:xrf5

本节程序如图:


image.png

问题描述

大家要添加一头牛,让它在场景中两个位置,大家都知道这么做
1、读取牛=node
2、申请MatrixTransform位置一 ,将其加牛node为子结点
3、设置MatrixTransform位置二,将其加牛node为子结点

上面牛就是共享结点,几乎没有人会将牛读取两次,分别加入到两个MatrixTranform当中。既然大家都知道,那么本文还有什么意义呢?

本文意义有二:
1、让大家看看究竟这样能改善多少。
2、正常的大家为了重复读牛,都会自己维护一个东西,今天为大家提供了一个独立于结点之外的新手段:在读取结点的时候判断当前结点是否已经被读取过了。

实现功能

1、按住CTRL,然后鼠标左键点击某船,则船消失,可以用来观察内存变化情况。这个小功能王锐大师还封了个小结构,大家也可以学习学习。

2、注释掉156 行osgDB::Registry::instance()->setReadFileCallback(sharer.get());,则所有的模型都是硬读取,不管是否已经读取了。否则在读取时会在ReadAndShareCallback类中判断此模型是否已经被读取过。下图左侧是使用共享结点,下图右侧是每次都读。可见内存占用差异很大。内存有两个,一个是private bytes是虚拟地址中的内存,一个是working set(工作内存)是占用的物理内存。

image.png

实现思路

1、class ReadAndShareCallback继承自public osgDB::ReadFileCallback可以保证每次readnodefile都会调用该callback,在callback中维护了一个map: ypedef std::map<std::string, osg::ref_ptr<osg::Node> > NodeMap;,这个map是名字与Node对应,也就是靠名字查找这个结点读取了没有,听起来不太靠谱,会意即可。读结点的时候先根据名字去这个map里查一下,有了就不读了:

       //先去map里查查有没有这个node
        osg::Node* node = getNodeByName(filename);
        if (!node)
        {
            osgDB::ReaderWriter::ReadResult rr =
                osgDB::Registry::instance()->readNodeImplementation(filename, options);
            if (rr.success()) _nodeMap[filename] = rr.getNode();
            return rr;
        }
        else
            std::cout << "[SHARING] The name " << filename << " is already added to the sharing list." << std::endl;
        return node;

2、这里面我们是从files.txt中读取结点的,其内容如下:


image.png

主要定义了模型的名称和位置,可以看到我们加了8个一模一样的模型在不同的位置。读取files.txt到场景中的代码如下:


void addFileList(osg::Group* root, const std::string& file)
{
    std::ifstream is(file.c_str());
    if (!is) return;

    while (!is.eof())
    {
        osg::Vec3 pos;
        std::string name;
        is >> name >> pos[0] >> pos[1] >> pos[2];

        osg::ref_ptr<osg::MatrixTransform> trans = new osg::MatrixTransform;
        trans->addChild(osgDB::readNodeFile(name));
        trans->setMatrix(osg::Matrix::translate(pos));
        trans->setName(c_removeMark);
        root->addChild(trans.get());
    }
}

其它 现在我们要在场景重复添加大量相同物体,只是在不同的位置,使用的往往是多实例的方法。后面也会讲到,多实例也是觖性能问题的利器。

以上的功能在OSG内部也有为了避免重复读取结点的机制,叫做ObjectCache,如下使用:

osg::ref_ptr<osgDB::Options> options = new osgDB::Options;
options->setObjectCacheHint( osgDB::Options::CACHE_NODES );
osg::Node* model = osgDB::readNodeFile( "cow.osg", options.get() );

读取图片时,可以使用CACHE_IMAGES和osgDB::readImageFile()。

以下是本节所有代码:

/* -*-c++-*- OpenSceneGraph Cookbook
 * Chapter 8 Recipe 3
 * Author: Wang Rui <wangray84 at gmail dot com>
*/

#include <osg/MatrixTransform>
#include <osgDB/ReadFile>
#include <osgViewer/ViewerEventHandlers>
#include <osgViewer/Viewer>
#include <fstream>
#include <iostream>

const std::string c_removeMark("Removable");

//读取文件的callback,设置该callback之后,所有的读取node的动作都会先走到这里
//我们设置这个callback的目的是要看看当前是否已经读取了该结点,它将所有读取的结点都
//存在了一个map中,每5秒看一下这个结点是不是还有人引用,没有人引用则删除掉这个结点
class ReadAndShareCallback : public osgDB::ReadFileCallback
{
public:
    virtual osgDB::ReaderWriter::ReadResult readNode(const std::string& filename, const osgDB::Options* options)
    {
        OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_shareMutex);
        osg::Node* node = getNodeByName(filename);
        if (!node)
        {
            osgDB::ReaderWriter::ReadResult rr =
                osgDB::Registry::instance()->readNodeImplementation(filename, options);
            if (rr.success()) _nodeMap[filename] = rr.getNode();
            return rr;
        }
        else
            std::cout << "[SHARING] The name " << filename << " is already added to the sharing list." << std::endl;
        return node;
    }

    void prune(int second)
    {
        if (!(second % 5))  // Prune the scene every 5 seconds
            return;

        OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_shareMutex);
        for (NodeMap::iterator itr = _nodeMap.begin(); itr != _nodeMap.end(); )
        {
            if (itr->second.valid())
            {
                if (itr->second->referenceCount() <= 1)
                {
                    std::cout << "[REMOVING] The name " << itr->first << " is removed from the sharing list." << std::endl;
                    itr->second = NULL;
                }
            }
            ++itr;
        }
    }

protected:
    osg::Node* getNodeByName(const std::string& filename)
    {
        NodeMap::iterator itr = _nodeMap.find(filename);
        if (itr != _nodeMap.end()) return itr->second.get();
        return NULL;
    }

    typedef std::map<std::string, osg::ref_ptr<osg::Node> > NodeMap;
    NodeMap _nodeMap;
    OpenThreads::Mutex _shareMutex;
};

//做一个点击事件的基类,当点住CTRL的情况下,左键单击模型,则会对这个模型调用doUserOperations
class PickHandler : public osgGA::GUIEventHandler
{
public:
    bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
    {
        if (ea.getEventType() != osgGA::GUIEventAdapter::RELEASE ||
            ea.getButton() != osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON ||
            !(ea.getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_CTRL))
            return false;

        osgViewer::View* viewer = dynamic_cast<osgViewer::View*>(&aa);
        if (viewer)
        {
            osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector =
                new osgUtil::LineSegmentIntersector(osgUtil::Intersector::WINDOW, ea.getX(), ea.getY());
            osgUtil::IntersectionVisitor iv(intersector.get());
            viewer->getCamera()->accept(iv);

            if (intersector->containsIntersections())
            {
                osgUtil::LineSegmentIntersector::Intersection result = *(intersector->getIntersections().begin());
                doUserOperations(result);
            }
        }
        return false;
    }
    virtual void doUserOperations(osgUtil::LineSegmentIntersector::Intersection& result) = 0;
};

//继承点击基类,当点中模型之后看它是否有父亲,有父亲的话就会从父亲中把它删除掉
class RemoveModelHandler : public PickHandler
{
public:
    RemoveModelHandler(ReadAndShareCallback* cb) : _callback(cb) {}

    virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
    {
        if (ea.getEventType() == osgGA::GUIEventAdapter::FRAME)
        {
            //每一帧都会调用一下pruce,将已经删除掉的孤点从读取结点的map中删除
            if (_callback.valid())
                _callback->prune((int)ea.getTime());
        }
        return PickHandler::handle(ea, aa);
    }

    virtual void doUserOperations(osgUtil::LineSegmentIntersector::Intersection& result)
    {
        for (osg::NodePath::iterator itr = result.nodePath.begin();
            itr != result.nodePath.end(); ++itr)
        {
            if ((*itr)->getName() == c_removeMark)
            {
                osg::Group* parent = ((*itr)->getNumParents() > 0 ? (*itr)->getParent(0) : NULL);
                if (parent) parent->removeChild((*itr));
                break;
            }
        }
    }

    osg::observer_ptr<ReadAndShareCallback> _callback;
};

void addFileList(osg::Group* root, const std::string& file)
{
    std::ifstream is(file.c_str());
    if (!is) return;

    while (!is.eof())
    {
        osg::Vec3 pos;
        std::string name;
        is >> name >> pos[0] >> pos[1] >> pos[2];

        osg::ref_ptr<osg::MatrixTransform> trans = new osg::MatrixTransform;
        trans->addChild(osgDB::readNodeFile(name));
        trans->setMatrix(osg::Matrix::translate(pos));
        trans->setName(c_removeMark);
        root->addChild(trans.get());
    }
}

int main(int argc, char** argv)
{
    osg::ref_ptr<ReadAndShareCallback> sharer = new ReadAndShareCallback;
    osgDB::Registry::instance()->setReadFileCallback(sharer.get());

    osg::ref_ptr<osg::Group> root = new osg::Group;
    addFileList(root.get(), "files.txt");

    osgViewer::Viewer viewer;
    viewer.setSceneData(root.get());
    viewer.addEventHandler(new RemoveModelHandler(sharer.get()));
    viewer.addEventHandler(new osgViewer::StatsHandler);
    return viewer.run();
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • 一.线性表 定义:零个或者多个元素的有限序列。也就是说它得满足以下几个条件:  ①该序列的数据元素是有限的。  ②...
    Geeks_Liu阅读 2,693评论 1 12
  • 一些概念 数据结构就是研究数据的逻辑结构和物理结构以及它们之间相互关系,并对这种结构定义相应的运算,而且确保经过这...
    Winterfell_Z阅读 5,708评论 0 13
  • 表情是什么,我认为表情就是表现出来的情绪。表情可以传达很多信息。高兴了当然就笑了,难过就哭了。两者是相互影响密不可...
    Persistenc_6aea阅读 124,506评论 2 7
  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 6,041评论 0 4