Запустить кэш-память процессора вручную в Java: возможно? необходимо? - PullRequest
0 голосов
/ 08 января 2019

Я пишу видеоигру в свободное время и у меня есть вопрос о согласованности данных при внедрении многопоточности.

На данный момент моя игра однопоточная и имеет простой игровой цикл, о чем говорится во многих руководствах:

while game window is not closed
{
    poll user input
    react to user input
    update game state
    render game objects
    flip buffers
}

Теперь я хочу добавить новую функцию в мою игру, где игрок может автоматизировать определенные задачи, которые являются длинными и утомительными, например, ходьба на большие расстояния (быстрое путешествие). Я мог бы просто «телепортировать» персонажа игрока к месту его назначения, но я бы предпочел не делать этого Вместо этого игра будет ускорена, и персонаж игрока будет фактически ходить, как если бы игрок делал это вручную. Преимущество этого состоит в том, что игровой мир будет взаимодействовать с персонажем игрока как обычно, и любые особые события, которые могут произойти, все же произойдут и немедленно остановят быстрое путешествие.

Чтобы реализовать эту функцию, я думал о чем-то вроде этого:

  • Начать новый поток (рабочий поток) и заставить этот поток постоянно обновлять состояние игры, пока персонаж игрока не достигнет своего пункта назначения
  • Пусть основной поток больше не обновляет состояние игры и не отображает игровые объекты как обычно, а вместо этого отображает ход поездки в более упрощенном виде
  • Использование синхронизированной очереди сообщений для связи основного потока и рабочего потока.
  • Когда быстрое путешествие закончено или отменено (из-за взаимодействия с игроком или по другим причинам), рабочий поток умрет и возобновит стандартный игровой цикл с основным потоком

В псевдокоде это может выглядеть так:

[main thread]
while game window is not closed
{
    poll user input

    if user wants to cancel fast travel
    {
        write to message queue player input "cancel"
    }

    poll message queue about fast travel status

    if fast travel finished or canceled
    {
        resume regular game loop
    } else {
        render travel status
        flip buffers
    }
}

[worker thread]
while (travel ongoing)
{
    poll message queue

    if user wants to cancel fast travel
    {
        write to message queue fast travel status "canceled"
        return
    }

    update game state

    if fast travel is interrupted by internal game event
    {
        write to message queue fast travel status "canceled"
        return
    }

    write to message queue fast travel status "ongoing"
}
if travel was finished
{
    write to message queue fast travel status "finished"
}

Очередь сообщений будет представлять собой некую двухканальную синхронизированную структуру данных. Может быть, два ArrayDeque с замком для каждого. Я вполне уверен, что это не будет слишком большой проблемой.

Больше всего меня беспокоит кеширование данных игры:

  • 1.a) Может ли рабочий поток, после запуска, видеть старые игровые данные, потому что основной поток может работать на другом ядре, которое кэшировало некоторые из его результатов?
  • 1.b) Если вышеприведенное верно: нужно ли мне объявлять каждое поле в данных игры как изменчивое, чтобы защитить себя с абсолютной гарантией от противоречивых данных?
  • 2) Правильно ли я полагаю, что производительность получит нетривиальный удар, если все поля изменчивы?
  • 3) Поскольку мне нужно только передавать данные между потоками в несколько и хорошо контролируемых моментов времени, можно ли будет заставить все кэши записывать обратно в основную память вместо использования изменяемых полей?
  • 4) Есть ли лучший подход? Возможно, моя концепция плохо продумана?

Спасибо за любую помощь и извините за большой кусок текста. Я подумал, что будет проще ответить на вопрос, если вы знаете предполагаемое использование.

1 Ответ

0 голосов
/ 09 января 2019

Поскольку мне нужно только передавать данные между потоками в несколько контролируемых моментов времени, можно ли будет принудительно заставить все кэши выполнять обратную запись в основную память вместо использования энергозависимых полей?

Нет. Это не так, как все это работает. Позвольте мне дать вам очень короткие ответы, чтобы объяснить, почему вы думаете об этом неправильно:

1.a) Может ли рабочий поток после запуска видеть старые игровые данные, потому что основной поток может работать на другом ядре, которое кэшировало некоторые из его результатов?

Конечно. Или это может быть по какой-то другой причине. Видимость памяти не гарантируется, поэтому вы не можете полагаться на нее, если не используете что-то гарантированное для обеспечения видимости памяти.

1.b) Если вышеприведенное верно: нужно ли мне объявлять каждое поле в данных игры как изменчивое, чтобы защитить себя с абсолютной гарантией от противоречивых данных?

Нет. Любой метод обеспечения видимости памяти будет работать. Вам не нужно делать это каким-то особым образом.

2) Правильно ли я полагаю, что производительность получит нетривиальный удар, если все поля будут нестабильными?

Возможно. Вероятно, это был бы худший из возможных способов сделать это.

3) Поскольку мне нужно только передавать данные между потоками в несколько и хорошо контролируемых моментов времени, можно ли будет принудительно заставить все кэши выполнять обратную запись в основную память вместо использования энергозависимых полей?

Нет. Поскольку нет операции «записать кэш обратно в память», которая обеспечивает видимость памяти. Ваша платформа может даже не иметь таких кэшей, и проблема может быть совсем в другом. Вы пишете код Java, вам не нужно думать о том, как работает ваш конкретный процессор, какие у него ядра или кэши или что-то в этом роде. Это одно из больших преимуществ использования языка с семантикой, которая гарантирована и не говорит о ядрах, кешах или чем-то подобном.

4) Есть ли лучший подход? Возможно, моя концепция плохо продумана?

Абсолютно. Вы пишете код Java. Используйте различные классы и функции синхронизации Java и полагайтесь на них, чтобы доказать семантику, которую они задокументировали. Даже не думайте о ядрах, кешах, сбросе памяти или чем-то подобном. Это аппаратные детали, о которых вам, как программисту на Java, даже не нужно думать.

Любая Java-документация, в которой говорится о ядрах, кэшах или сбросах в память, на самом деле не говорит о реальных ядрах, кэшах или сбросах в память. Это просто дает вам несколько способов подумать о гипотетическом оборудовании, чтобы вы могли обдумать, почему видимость памяти и общий порядок не всегда работают идеально сами по себе. Ваш реальный процессор или платформа могут иметь совершенно разные проблемы, которые не имеют никакого сходства с этим гипотетическим оборудованием (А у реальных процессоров и систем согласованность кэша гарантируется аппаратными средствами, и их проблемы с отображением / упорядочением на самом деле совершенно разные!)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...